【发布时间】:2020-12-10 07:32:12
【问题描述】:
我正在编写一个包含少量粒子(通常为 3 个,不超过 5 个)的物理模拟。
在精简版中,我的代码结构如下:
#include<iostream>
class Particle{
double x; // coordinate
double m; // mass
};
void performStep(Particle &p, double &F_external){
p.x += -0.2*p.x + F_external/p.m; // boiled down, in reality complex calculation, not important here
}
int main(){
dt = 0.001; // time step, not important
Particle p1;
p1.x = 5; // some random number for initialization, in reality more complex but not important here
p.m = 1;
Particle p2;
p2.x = -1; // some random numbersfor initialization, in reality more complex but not important here
p.m = 2;
Particle p3;
p3.x = 0; // some random number for initialization, in reality more complex but not important here
p.m = 3;
double F_external = 0; // external forces
for(unsigned long long int i=0; i < 10000000000; ++i){ // many steps, typically 10e9
F_external = sin(i*dt);
performStep(p1, F_external);
performStep(p2, F_external);
performStep(p3, F_external);
}
std::cout << "p1.x: " << p1.x << std::endl;
std::cout << "p2.x: " << p2.x << std::endl;
std::cout << "p3.x: " << p3.x << std::endl;
}
我已通过clock() 确定performStep(p, F_external) 调用是我代码中的瓶颈)。
当我尝试进行内联计算时,即用 p1.x += -0.2*p1.x + F_external/p1.m; 替换 performStep(p1, F_external) 时,计算突然快了大约 2 倍。请注意,performStep() 实际上是大约 20 行的大约 60 次基本算术计算,所以如果我只为每个粒子内联它,代码就会变得非常臃肿。
为什么会这样?我正在使用 MinGW64/g++ 和 -O2 标志进行编译。我以为编译器会优化这些东西?
编辑:
这是被调用的函数。请注意,实际上,我使用几个不同的外力计算所有三个坐标 x、y、z。未通过函数传递的变量是SimulationRun 的成员。该算法是四阶leapfrog算法。
void SimulationRun::performLeapfrog_z(const unsigned long long int& i, const double& x, const double& y, double& z, const double& vx, const double& vy, double& vz, const double& qC2U0,
const double& U0, const double& m, const double& C4, const double& B2, const double& f_minus, const double& f_z, const double& f_plus, const bool& bool_calculate_xy,
const double& Find, const double& Fheating) {
// probing for C4 == 0 and B2 == 0 saves some computation time
if (C4 == 0) {
Fz_C4_Be = 0;
}
if (B2 == 0 || !bool_calculate_xy) {
Fz_B2_Be = 0;
}
z1 = z + c1 * vz * dt;
if (C4 != 0 && !bool_calculate_xy) {
Fz_C4_Be = (-4) * q * C4 * U0 * z1 * z1 * z1;
}
else if (C4 != 0 && bool_calculate_xy) {
Fz_C4_Be = q * C4 * U0 * (-4 * z1 * z1 * z1 + 6 * z1 * (x * x + y * y));
}
if (B2 != 0 && bool_calculate_xy) {
Fz_B2_Be = q * B2 * (-vx * z1 * y + vy * z1 * x);
}
acc_z1 = (qC2U0 * (-2) * z1 + Find + Fz_C4_Be + Fz_B2_Be + Fheating) / m;
vz1 = vz + d1 * acc_z1 * dt;
z2 = z1 + c2 * vz1 * dt;
if (C4 != 0 && !bool_calculate_xy) {
Fz_C4_Be = (-4) * q * C4 * U0 * z2 * z2 * z2;
}
else if (C4 != 0 && bool_calculate_xy) {
Fz_C4_Be = q * C4 * U0 * (-4 * z2 * z2 * z2 + 6 * z2 * (x * x + y * y));
}
if (B2 != 0 && bool_calculate_xy) {
Fz_B2_Be = q * B2 * (-vx * z2 * y + vy * z2 * x);
}
acc_z2 = (qC2U0 * (-2) * z2 + +Find + Fz_C4_Be + Fz_B2_Be + Fheating) / m;
vz2 = vz1 + d2 * acc_z2 * dt;
z3 = z2 + c3 * vz2 * dt;
if (C4 != 0 && !bool_calculate_xy) {
Fz_C4_Be = (-4) * q * C4 * U0 * z3 * z3 * z3;
}
else if (C4 != 0 && bool_calculate_xy) {
Fz_C4_Be = q * C4 * U0 * (-4 * z3 * z3 * z3 + 6 * z3 * (x * x + y * y));
}
if (B2 != 0 && bool_calculate_xy) {
Fz_B2_Be = q * B2 * (-vx * z3 * y + vy * z3 * x);
}
acc_z3 = (qC2U0 * (-2) * z3 + Find + Fz_C4_Be + Fz_B2_Be + Fheating) / m;
vz3 = vz2 + d3 * acc_z3 * dt;
z = z3 + c4 * vz3 * dt;
vz = vz3;
}
【问题讨论】:
-
旁注:你的循环不是要求 10^10 次迭代,这会溢出
unsigned int吗? -
您可能想发布
performStep()的其余部分。传递引用比在 C 中传递一个简单的指针更复杂。我并没有想到这将如何影响performStep()的效率,但至少看看引用的使用方式可能会指向这里的一些聪明人正朝着正确的方向前进。 -
或者使用
-Ofast继续编译,并提供编译器必须提供的所有内容。 -
您说的是
boiled down, in reality complex calculation, not important here,但实际上它很关键,因为函数的复杂性是编译器用来决定是否内联的主要标准之一,请提供minimal reproducible example。例如。您的代码确实与 gcc 内联:godbolt.org/z/E5oeW4 -
另外,如果您使用简单类型,请不要使用 const 引用,只需按值使用它们。它将简化代码并且编译器不必担心取消引用它们(尽管存在差异,您可以将它们视为与指针相同的方式,尽管不必使用箭头 -> ,但它们必须被取消引用)。取消引用是有代价的,在这种情况下是不需要的
标签: c++ performance methods