无法在不丢失输出的情况下使用 Loop 展开优化循环

Can't optimize loops with Loop unrolling without losing my output

提问人:PeRaDi 提问时间:10/17/2023 更新时间:10/17/2023 访问量:83

问:

我正在尝试使用循环展开技术将这个函数优化到极限,但我无法弄清楚为什么我的方法会从原始函数修改我的输出(矩阵“a”)。

现在我正在尝试在每个循环中步入 2。谁能帮我解决这个问题? (这是一个取自git/FoleyLab/MolecularDynamics/的C++程序,这是一个简单的分子动力学程序,用于模拟伦纳德-琼斯粒子的真实气体特性)

**原有功能: **

void computeAccelerations()
{
    int i, j, k;
    double f, rSqd, rSqd4, rSqd7;
    double result1, result2, result3;
    double rij[3]; // position of i relative to j

    for (i = 0; i < N; i++)
    {
        a[i][0] = 0;
        a[i][1] = 0;
        a[i][2] = 0;
    }

    for (i = 0; i < N - 1; i++)
    {
        for (j = i + 1; j < N; j++)
        {
            result1 = r[i][0] - r[j][0];
            result2 = r[i][1] - r[j][1];
            result3 = r[i][2] - r[j][2];
            rSqd = result1 * result1 + result2 * result2 + result3 * result3;

            rSqd4 = rSqd * rSqd * rSqd * rSqd;
            rSqd7 = rSqd4 * rSqd * rSqd * rSqd;

            f = 24 * (2 / rSqd7 - 1 / rSqd4);

            a[i][0] += result1 * f;
            a[j][0] -= result1 * f;
            a[i][1] += result2 * f;
            a[j][1] -= result2 * f;
            a[i][2] += result3 * f;
            a[j][2] -= result3 * f;
        }
    }
}

**我的方法: **

void computeAccelerations()
{
    int i, j, k;
    double f, rSqd, rSqd4, rSqd7;
    double result1, result2, result3, result4, result5, result6;
    double rij[6]; // position of i relative to j

    for (i = 0; i < N; i++)
    {
        a[i][0] = 0;
        a[i][1] = 0;
        a[i][2] = 0;
    }

    for (i = 0; i < N - 1; i += 2)
    {
        for (j = i + 1; j < N; j += 2)
        {
            result1 = r[i][0] - r[j][0];
            result2 = r[i][1] - r[j][1];
            result3 = r[i][2] - r[j][2];
            rSqd = result1 * result1 + result2 * result2 + result3 * result3;

            rSqd4 = rSqd * rSqd * rSqd * rSqd;
            rSqd7 = rSqd4 * rSqd * rSqd * rSqd;

            f = 24 * (2 / rSqd7 - 1 / rSqd4);

            a[i][0] += result1 * f;
            a[j][0] -= result1 * f;
            a[i][1] += result2 * f;
            a[j][1] -= result2 * f;
            a[i][2] += result3 * f;
            a[j][2] -= result3 * f;

            result4 = r[i + 1][0] - r[j + 1][0];
            result5 = r[i + 1][1] - r[j + 1][1];
            result6 = r[i + 1][2] - r[j + 1][2];
            rSqd = result4 * result4 + result5 * result5 + result6 * result6;

            rSqd4 = rSqd * rSqd * rSqd * rSqd;
            rSqd7 = rSqd4 * rSqd * rSqd * rSqd;

            f = 24 * (2 / rSqd7 - 1 / rSqd4);

            a[i + 1][0] += result4 * f;
            a[j + 1][0] -= result4 * f;
            a[i + 1][1] += result5 * f;
            a[j + 1][1] -= result5 * f;
            a[i + 1][2] += result6 * f;
            a[j + 1][2] -= result6 * f;
        }
    }
}
C 优化 并行处理 循环展开

评论

0赞 user3386109 10/17/2023
您更改了外部循环,但未复制内部循环。还有您需要涵盖的奇偶极端情况。
1赞 Jérôme Richard 10/17/2023
如果是编译时常量,则默认情况下,Clang 和 ICC 等编译器已经展开代码。在这种情况下,手动展开循环是一个坏主意,因为它会降低代码的可读性,更难维护,并且经常引入令人讨厌的错误,更不用说它会减慢速度,因为编译器在某些情况下更难矢量化循环(涉及 SLP 矢量化)。如果不是编译时常量,则可以使用编译时常量展开带有附加内部循环的循环。此外,一些编译器有这个。NNpragma
0赞 0___________ 10/17/2023
@JérômeRichard GCC并不像你想象的那么好 godbolt.org/z/eEvYqxqaT
1赞 Jérôme Richard 10/17/2023
什么是编译标志?请注意,在没有快速数学优化的情况下可以更快地使用(与 相同)。同样,可以替换为 。最后,使用内存布局存储数据效率低下。它阻止了 SIMD 优化,而 SIMD 优化远比展开更重要。请考虑使用 SIMD 友好的布局(如 3 个数组:每个组件一个)。rSqd * rSqd * rSqd * rSqd(rSqd * rSqd) * (rSqd * rSqd)rSqd724 * (2 / rSqd7 - 1 / rSqd4)(24 * 2) / rSqd7 - 24 / rSqd4(x, y, z)
1赞 Jérôme Richard 10/17/2023
@0___________是的,GCC 不利于展开。这是一个众所周知的问题,持续了一段时间......这就是为什么我在上面的评论中没有提到它的原因。这很可悲,因为 GCC 经常用于 HPC,而 ICC 不是......无论如何,我不希望在这里使用展开的现代 x86-64 机器上有显着的速度。使用 GCC 对 Skylake 进行快速检查证实了这一期望:FMA 单元和 uops 问题都是瓶颈。因此,展开代码不会修复 FMA 瓶颈,并且会导致非常小的加速。使用 Clang,展开会有所帮助,但作用不大 (<15%)。

答: 暂无答案