性能数组乘法 Pearson

Posted

技术标签:

【中文标题】性能数组乘法 Pearson【英文标题】:Performance array multiplication Pearson 【发布时间】:2016-08-15 02:44:21 【问题描述】:

我多次计算Pearson correlation(平均用户/项目评级),使用我当前的代码性能非常糟糕:

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 的标准 Pearson 相关性,但这是对标准的修改,无法使用它。有没有办法加快速度?如何针对时间复杂度进行优化?

【问题讨论】:

我觉得这个问题最好在这里问codereview.stackexchange.com 我们可以通过查看代码做出一些假设,但我们不知道这些假设实际上提高了多少性能。您应该通过分析器运行它以真正查看导致缓慢的原因。 【参考方案1】:

在 MSE 上添加一些答案——将 Pow(x,2) 更改为 diff*diff 绝对是您想要做的事情,您可能还希望避免在最内层循环中进行不必要的边界检查。这可以使用pointers in 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;
    

【讨论】:

【参考方案2】:

我在您的代码中看到的唯一可能的优化是在以下代码中,如果您仍在寻找更好的性能,那么您可能需要使用SIMD vectorization。它将允许您使用 CPU 的全部计算能力

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;
    

【讨论】:

【参考方案3】:

如果可能,解决性能问题的最佳方法可能是避免计算尽可能多的相关性。如果您将相关性用作另一个计算的一部分,则可以使用数学来消除对其中一些的需求。

您还应该考虑是否可以使用 Pearson 相关性的平方而不是 Pearson 相关性本身。这样一来,您就可以将呼叫保存到 Math.Sqrt(),这通常非常昂贵。

如果确实需要求平方根,则应再次使用sqrtDenomXsqrtDenomY,而不是重新计算平方根。

【讨论】:

以上是关于性能数组乘法 Pearson的主要内容,如果未能解决你的问题,请参考以下文章

如何进一步优化矩阵乘法的性能?

矩阵乘法性能 numpy 和 eigen c++

CUDA矩阵乘法的性能

numpy 矩阵乘法的奇怪性能结果

常用器件乘法器性能比较

JavaScript 性能——除法还是乘法? /对*