Производительность умножения массива Пирсона

Я вычисляю корреляцию Пирсона (средний рейтинг пользователя / элемента) много раз, используя мой текущий код, производительность очень плохая:

public double ComputeCorrelation(double[] x, double[] y, double[] meanX, double[] meanY)
        {
            if (x.Length != y.Length)
                throw new ArgumentException("values must be the same length");

            double sumNum = 0;
            double sumDenom = 0;
            double denomX = 0;
            double denomY = 0;

            for (int a = 0; a < x.Length; a++)
            {
                sumNum += (x[a] - meanX[a]) * (y[a] - meanY[a]);
                denomX += Math.Pow(x[a] - meanX[a], 2);
                denomY += Math.Pow(y[a] - meanY[a], 2);
            }

            var sqrtDenomX = Math.Sqrt(denomX);
            var sqrtDenomY = Math.Sqrt(denomY);

            if (sqrtDenomX == 0 || sqrtDenomY == 0) return 0;

            sumDenom = Math.Sqrt(denomX) * Math.Sqrt(denomY);

            var correlation = sumNum / sumDenom;

            return correlation;
        }

Я использую стандартную корреляцию Пирсона с MathNet.Numerics, но это модификация стандарта, и его невозможно использовать. Есть ли способ ускорить его? Как его можно оптимизировать с точки зрения сложности времени?

3 ответа

Решение

Добавление ответа на MSE - изменение Pow(x,2) в diff*diff это определенно то, что вы хотите сделать, вы также можете избежать ненужной проверки границ в самом внутреннем цикле. Это может быть сделано с помощью указателей в C#.

Можно сделать так:

    public unsafe double ComputeCorrelation(double[] x, double[] y, double[] meanX, double[] meanY)
    {
        if (x.Length != y.Length)
            throw new ArgumentException("values must be the same length");

        double sumNum = 0;
        double sumDenom = 0;
        double denomX = 0;
        double denomY = 0;
        double diffX;
        double diffY;

        int len = x.Length;

        fixed (double* xptr = &x[0], yptr = &y[0], meanXptr = &meanX[0], meanYptr = &meanY[0])
        {
            for (int a = 0; a < len; a++)
            {
                diffX = (xptr[a] - meanXptr[a]);
                diffY = (yptr[a] - meanYptr[a]);
                sumNum += diffX * diffY;
                denomX += diffX * diffX;
                denomY += diffY * diffY;
            }
        }

        var sqrtDenomX = Math.Sqrt(denomX);
        var sqrtDenomY = Math.Sqrt(denomY);

        if (sqrtDenomX == 0 || sqrtDenomY == 0) return 0;

        sumDenom = sqrtDenomX * sqrtDenomY;

        var correlation = sumNum / sumDenom;

        return correlation;
    }

Лучший способ решить проблемы с производительностью - это, по возможности, избегать вычисления как можно большего количества корреляций. Если вы используете корреляции как часть другого вычисления, может быть возможно использовать математику, чтобы устранить необходимость в некоторых из них.

Вам также следует подумать, сможете ли вы использовать квадрат корреляции Пирсона вместо самой корреляции Пирсона. Таким образом, вы можете сохранить свои звонки на Math.Sqrt(), которые обычно довольно дороги.

Если вам нужно взять квадратный корень, вы должны использовать sqrtDenomX а также sqrtDenomY опять же, а не пересчитать квадратные корни.

Единственная возможная оптимизация, которую я вижу в вашем коде, заключается в следующем коде. Если вы все еще ищете лучшую производительность, вы можете использовать векторизацию SIMD. Это позволит вам использовать полную вычислительную мощность процессора

public double ComputeCorrelation(double[] x, double[] y, double[] meanX, double[] meanY)
    {
        if (x.Length != y.Length)
            throw new ArgumentException("values must be the same length");

        double sumNum = 0;
        double sumDenom = 0;
        double denomX = 0;
        double denomY = 0;
        double diffX;
        double diffY;

        for (int a = 0; a < x.Length; a++)
        {
            diffX = (x[a] - meanX[a]);
            diffY = (y[a] - meanY[a]);
            sumNum += diffX * diffY;
            denomX += diffX * diffX;
            denomY += diffY * diffY;
        }

        var sqrtDenomX = Math.Sqrt(denomX);
        var sqrtDenomY = Math.Sqrt(denomY);

        if (sqrtDenomX == 0 || sqrtDenomY == 0) return 0;

        sumDenom = sqrtDenomX * sqrtDenomY;

        var correlation = sumNum / sumDenom;

        return correlation;
    }
Другие вопросы по тегам