优化四重嵌套" for"环

时间:2017-03-02 19:37:51

标签: c++ performance for-loop nested

我正在用c ++开发一个2D数值模型,我想加快一个特定的成员函数,这会降低我的代码速度。该函数需要遍历模型中的每个i,j网格点,然后在lm上的每个网格点执行双重求和。功能如下:

int Class::Function(void) {
    double loadingEta;
    int i,j,l,m;

    //etaLatLen=64, etaLonLen=2*64
    //l_max = 12

    for (i=0; i<etaLatLen; i++) {
        for (j=0; j < etaLonLen; j++) {
            loadingEta = 0.0;
            for (l=0; l<l_max+1; l++) {
                for (m=0; m<=l; m++) {
                    loadingEta += etaLegendreArray[i][l][m] * (SH_C[l][m]*etaCosMLon[j][m] + SH_S[l][m]*etaSinMLon[j][m]);
                }
            }
            etaNewArray[i][j] = loadingEta;
        }
    }

    return 1;
}

我一直试图改变循环次序以加快速度,但无济于事。任何帮助将非常感激。谢谢!

编辑1:

所有五个数组都在我的类的构造函数中分配,如下所示:

etaLegendreArray = new double**[etaLatLen];
for (int i=0; i<etaLatLen; i++) {
    etaLegendreArray[i] = new double*[l_max+1];
    for (int l=0; l<l_max+1; l++) {
        etaLegendreArray[i][l] = new double[l_max+1];
    }
}

SH_C = new double*[l_max+1];
SH_S = new double*[l_max+1];
for (int i=0; i<l_max+1; i++) {
    SH_C[i] = new double[l_max+1]; 
    SH_S[i] = new double[l_max+1];
}

etaCosMLon = new double*[etaLonLen];
etaSinMLon = new double*[etaLonLen];
for (int j=0; j<etaLonLen; j++) {
    etaCosMLon[j] = new double[l_max+1];
    etaSinMLon[j] = new double[l_max+1];
}

如果这些是一维数组而不是多维数,也许会更好?

2 个答案:

答案 0 :(得分:1)

在这里跳进X-Y地区。让我们尝试加速数据访问,而不是加快算法速度。

etaLegendreArray = new double**[etaLatLen];
for (int i=0; i<etaLatLen; i++) {
    etaLegendreArray[i] = new double*[l_max+1];
    for (int l=0; l<l_max+1; l++) {
        etaLegendreArray[i][l] = new double[l_max+1];
    }
}

不创建double的3D数组。它创建了指向double数组指针数组的指针数组。每个阵列都有自己的内存块,谁知道它将存储在哪里。这导致数据结构具有所谓的“poor spacial locality”。结构的所有部分可以遍布整个地方。在3D阵列中,您可以跳到三个不同的位置,只是为了找出您的价值所在。

因为模拟3D阵列所需的许多存储块可能彼此无法靠近,所以CPU可能无法提前有效地加载缓存(高速存储器)并且必须停止它的有用工作。做和等待访问较慢的存储,可能更频繁地RAM。这是一个很好的高级article on how much this can hurt表演。

另一方面,如果整个数组在一个内存块中,是“连续的”,CPU可以读取更大的内存块,也许所有内存,它需要一次性进入缓存。此外,如果编译器知道程序将使用的内存全部在一个大块中,它可以执行各种常规优化,使您的程序更快。

那么我们如何获得一个所有内存块的3D数组呢?如果尺寸是静态的,这很容易

double etaLegendreArray[SIZE1][SIZE2][SIZE3];

这看起来不是你的情况,所以你要做的是分配1D数组,因为它将是一个连续的内存块。

double * etaLegendreArray= new double [SIZE1*SIZE2*SIZE3];

手动进行数组索引数学

etaLegendreArray[(x * SIZE2 + y) * SIZE3 + z] = data;

看起来所有额外的数学应该慢一点,对吧?事实证明,每次使用[]时,编译器都会隐藏看起来很像你的数学。你几乎没有失去任何东西,当然也没有失去一个不必要的cache miss

但是在整个地方重复这个数学是疯了,迟早你会搞砸尽管可读性的消耗不会让你先死亡,所以你真的想把1D数据包装成一个class to helper为你处理数学。一旦你这样做,你也可以让那个类处理分配和释放,这样你就可以利用all that RAII goodness。遍布fornew的{​​{1}}圈不再有delete个圈。它全部被包裹起来并用弓打结。

Here is an example of a 2D Matrix class easily extendable to 3D.将以可预测且缓存友好的方式处理您可能需要的基本功能。

答案 1 :(得分:0)

如果CPU支持并且编译器足够优化,你可能会从the C99 fma(融合乘法 - 加法)函数中获得一些小的收益,来转换你的两步操作(乘法,然后加)一步到位的操作。它还可以提高准确性,因为对融合操作只进行一次浮点舍入,而不是一次乘法和一次加法。

假设我正确读取它,你可以改变你最内层循环的表达式:

loadingEta += etaLegendreArray[i][l][m] * (SH_C[l][m]*etaCosMLon[j][m] + SH_S[l][m]*etaSinMLon[j][m]);

to(注意暂不使用+=,它已合并到fma)中:

loadingEta = fma(etaLegendreArray[i][l][m], fma(SH_C[l][m], etaCosMLon[j][m], SH_S[l][m]*etaSinMLon[j][m]), loadingEta);

我不希望任何神奇的性能,但它可能会有所帮助(再次,只有优化足够让编译器内联硬件指令才能完成工作;如果它调用库函数,那么你'将失去对函数调用开销的任何改进)。同样,它应该通过避免你发生的两个舍入步骤来提高准确性。

请注意some compilers with appropriate compilation flags, they'll convert your original code to hardware FMA instructions for you;如果这是一个选项,我会选择它,因为(正如你所看到的)fma函数往往会降低代码的可读性。

您的编译器也可能提供浮点指令的矢量化版本,这可能会有效地提高性能(请参阅自动转换为FMA的上一个链接)。

大多数其他改进都需要有关目标的更多信息,正在使用的输入数组的性质等。简单的线程可能会获得一些东西,OpenMP pragma可能是一种简化并行化循环的方法(s) )。