我正在尝试优化n体算法,我发现最昂贵的功能是这样的:
run
使用性能记录,我可以看到除法是最昂贵的指令,该指令的复杂度为O(n ^ 2),但我真的不知道如何优化它。
答案 0 :(得分:6)
转换
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
进入
for(int i=0; i<N;i++)
for(int j=i+1;j<N;j++)
进行重组以利用SIMD运算符,这可以使吞吐量提高四倍。
使用OpenMP在CPU上并行化循环,或者通过分流到GPU(OpenMP 4.5+)并行化循环。
了解有关Barnes-Hut algorithm的知识,该方法将粒子分组以实现 O(N log N)复杂度(低于您的 O(N ^ 2))。
答案 1 :(得分:2)
对于SIMD来说,这实际上是一个不错的选择。值得注意的是:
real s = jMass / POW(distSqr,3.0/2.0);
如果您取消此功能,则可以重构为:(删除一个部分)
real s = jMass * POW(distSqr, -3.0/2.0);
现在值得注意的是,由于要处理非常简单的指数,因此您可以在此处完全删除对pow的调用。所以...
real s = jMass * std::sqrt(distSqr) / (distSqr * distSqr);
如果您知道自己的权力定律,则可以在此处执行其他重构步骤:
real s = jMass / (std::sqrt(distSqr) * distSqr);
现在运气不错,您的编译器应该应该已经为您执行了此转换(通常需要-O2和-ffast-math)。例: https://godbolt.org/z/8YqFYA
这很好的原因是,现在您已经从代码中完全删除了cmath调用。这使得掉落到simd之类的东西非常容易,如果您碰巧使用clang或gcc,则非常容易。例如
#include <immintrin.h>
typedef __m256 real;
struct real3 { real x, y, z; };
// i had to make up a value
const __m256 SOFTENING_SQUARED = _mm256_set1_ps(1.23f);
real3 bodyBodyInteraction(real iPosx, real iPosy, real iPosz,
real jPosx, real jPosy, real jPosz, real jMass)
{
real rx, ry, rz;
rx = jPosx - iPosx;
ry = jPosy - iPosy;
rz = jPosz - iPosz;
real distSqr = rx*rx+ry*ry+rz*rz;
distSqr += SOFTENING_SQUARED;
real s = jMass / (_mm256_sqrt_ps(distSqr) * distSqr);
real3 f;
f.x = rx * s;
f.y = ry * s;
f.z = rz * s;
return f;
}
而在螺栓中: