HW04
作业描述
加速程序
思路
对step()加速。首先改写成SOA结构,并用std::array
替换std::vector
constexpr size_t N = 48
using ArrF = std::array<float, N>;
struct Star {
ArrF px, py, pz;
ArrF vx, vy, vz;
ArrF mass;
};
Star stars;
提取运算中不变量,提前计算出结果。除法用乘法代替。然后为了方便编译器优化,把复杂的循环体拆成多个执行简单计算的循环体。
为了减小访问内存的开销,用临时变量记录累加值。
数组访问全部改为size_t类型。
void step() {
ArrF dx, dy, dz, d2;
for (size_t i = 0; i < N; ++i) {
for (size_t j = 0; j < N; ++j) {
dx[j] = stars.px[j] - stars.px[i];
dx[j] *= dx[j];
}
for (size_t j = 0; j < N; ++j) {
dy[j] = stars.py[j] - stars.py[i];
dy[j] *= dy[j];
}
for (size_t j = 0; j < N; ++j) {
dz[j] = stars.pz[j] - stars.pz[i];
dz[j] *= dz[j];
}
for (size_t j = 0; j < N; ++j) {
d2[j] = dx[j] + dy[j] + dz[j] + eps * eps;
d2[j] *= std::sqrt(d2[j]);
d2[j] = 1 / d2[j];
}
float vx = 0, vy = 0, vz = 0;
float fac = stars.mass[i] * G_dt;
for (size_t j = 0; j < N; ++j) {
vx += dx[j] * d2[j] * fac;
vy += dy[j] * d2[j] * fac;
vz += dz[j] * d2[j] * fac;
}
stars.vx[i] += vx;
stars.vy[i] += vy;
stars.vz[i] += vz;
}
for (size_t i = 0; i < N; ++i) {
stars.px[i] += stars.vx[i] * dt;
}
for (size_t i = 0; i < N; ++i) {
stars.py[i] += stars.vy[i] * dt;
}
for (size_t i = 0; i < N; ++i) {
stars.pz[i] += stars.vz[i] * dt;
}
}
经过测试利用#pragma GCC unroll
进行循环展开并不会带来性能上的提升。
有可能是指令本身已经比较 多导致的。
在CMakeLists.txt
中开启O3优化set(CMAKE_BUILD_TYPE Release)
同时设置-ffast-math
:target_compile_options(main PUBLIC -ffast-math -march=native)
优化中最重要的两项:SOA和-ffast-math,可以分开测试单独改变其中某一项多带来的加速比。
对于是否需要把复杂的循环语句进行拆解,其实是不一定的,我看到了别人的作业中,只是单纯的利用临时变量来代替循环中的内存访问,也带来了很好的加速,至于原理我也不是很清楚。
下面是结果对比:
源程序:
加速后: