编辑澄清:最初发布时,有两个问题:
第二个问题已经解决:只有在调试器下运行时才会出现随机性。
这个问题的其余部分应该被理解为关于上面的第一个要点,以及在VC ++ 2010 Express的发布模式下运行,优化“最大化速度”和“支持快速代码”。
评论部分仍然有一些评论谈论第二点,但现在可以忽略它们。
我有一个模拟,如果我在运行实际模拟的while循环中添加一个简单的if语句,性能下降大约三倍(我在while循环中运行了很多计算,n-body gravity对于太阳系而言除了其他东西)即使if语句几乎从未执行过:
if (time - cb_last_orbital_update > 5000000)
{
cb_last_orbital_update = time;
}
time
和cb_last_orbital_update
都是double
类型,并在main函数的开头定义,其中if语句也是如此。通常我也想在那里运行计算,但如果我删除它们没有任何区别。上面的if语句对性能有同样的影响。
变量time
是模拟时间,它在开始时以0.001步增加,因此在第一次执行if语句之前需要很长时间(我还包括打印消息以查看是否它正在被执行,但它不是,或者至少只有它应该被执行)。无论如何,即使在模拟的最初几分钟内尚未执行一次,性能也会下降3倍。如果我注释掉这行
cb_last_orbital_update = time;
然后再次运行得更快,所以不是检查
time - cb_last_orbital_update > 5000000
要么,这绝对是将当前模拟时间写入此变量的简单行为。
此外,如果我将当前时间写入另一个变量而不是cb_last_orbital_update
,则性能不会下降。因此,这可能是一个问题,为变量分配一个新值,用于检查是否应该执行“if”?这些都是在黑暗中拍摄的。
免责声明:我对编程很陌生,对所有文字都很抱歉。
我正在使用Visual C ++ 2010 Express,停用stdafx.h
预编译头函数也没有任何区别。
编辑:该计划的基本结构。请注意,除了在while循环结束时(time += time_interval;
),time
已更改。此外,cb_last_orbital_update
只有3次出现:声明/初始化,加上导致问题的if语句中的两次。
int main(void)
{
...
double time = 0;
double time_interval = 0.001;
double cb_last_orbital_update = 0;
F_Rocket_Preset(time, time_interval, ...);
while(conditions)
{
Rocket[active].Stage[Rocket[active].r_stage].F_Update_Stage_Performance(time, time_interval, ...);
Rocket[active].F_Calculate_Aerodynamic_Variables(time);
Rocket[active].F_Calculate_Gravitational_Forces(cb_mu, cb_pos_d, time);
Rocket[active].F_Update_Rotation(time, time_interval, ...);
Rocket[active].F_Update_Position_Velocity(time_interval, time, ...);
Rocket[active].F_Calculate_Orbital_Elements(cb_mu);
F_Update_Celestial_Bodies(time, time_interval, ...);
if (time - cb_last_orbital_update > 5000000.0)
{
cb_last_orbital_update = time;
}
Rocket[active].F_Check_Apoapsis(time, time_interval);
Rocket[active].F_Status_Check(time, ...);
Rocket[active].F_Update_Mass (time_interval, time);
Rocket[active].F_Staging_Check (time, time_interval);
time += time_interval;
if (time > 3.1536E8)
{
std::cout << "\n\nBreak main loop! Sim Time: " << time << std::endl;
break;
}
}
...
}
编辑2:
Here是汇编代码的不同之处。左边是带有
行的快速代码cb_last_orbital_update = time;
取消注释,右边是慢线代码。
编辑4:
所以,我发现到目前为止似乎工作正常的解决方法:
int cb_orbit_update_counter = 1; // before while loop
if(time - cb_orbit_update_counter * 5E6 > 0)
{
cb_orbit_update_counter++;
}
编辑5:
虽然该解决方法确实有效,但它只能与使用__declspec(noinline)
结合使用。我刚刚再次从函数声明中删除了那些,以查看它是否会发生任何变化,而且确实如此。
__declspec(noinline)
移除到此函数时,我追踪了导致性能降低的罪魁祸首,该函数正在if
内执行:
__declspec(noinline) std::string F_Get_Body_Name(int r_body)
{
switch (r_body)
{
case 0:
{
return ("the Sun");
}
case 1:
{
return ("Mercury");
}
case 2:
{
return ("Venus");
}
case 3:
{
return ("Earth");
}
case 4:
{
return ("Mars");
}
case 5:
{
return ("Jupiter");
}
case 6:
{
return ("Saturn");
}
case 7:
{
return ("Uranus");
}
case 8:
{
return ("Neptune");
}
case 9:
{
return ("Pluto");
}
case 10:
{
return ("Ceres");
}
case 11:
{
return ("the Moon");
}
default:
{
return ("unnamed body");
}
}
}
if
现在也不只是增加计数器:
if(time - cb_orbit_update_counter * 1E7 > 0)
{
F_Update_Orbital_Elements_Of_Celestial_Bodies(args);
std::cout << F_Get_Body_Name(3) << " SMA: " << cb_sma[3] << "\tPos Earth: " << cb_pos_d[3][0] << " / " << cb_pos_d[3][1] << " / " << cb_pos_d[3][2] <<
"\tAlt: " << sqrt(pow(cb_pos_d[3][0] - cb_pos_d[0][0],2) + pow(cb_pos_d[3][1] - cb_pos_d[0][1],2) + pow(cb_pos_d[3][2] - cb_pos_d[0][2],2)) << std::endl;
std::cout << "Time: " << time << "\tcb_o_h[3]: " << cb_o_h[3] << std::endl;
cb_orbit_update_counter++;
}
我单独从函数__declspec(noinline)
删除F_Get_Body_Name
,代码变慢。同样,如果我删除此函数的执行或再次添加__declspec(noinline)
,代码运行得更快。所有其他功能仍然有__declspec(noinline)
。
编辑7: 所以我将开关功能改为
const std::string cb_names[] = {"the Sun","Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto","Ceres","the Moon","unnamed body"}; // global definition
const int cb_number = 12; // global definition
std::string F_Get_Body_Name(int r_body)
{
if (r_body >= 0 && r_body < cb_number)
{
return (cb_names[r_body]);
}
else
{
return (cb_names[cb_number]);
}
}
并且还使代码的另一部分更加苗条。该程序现在运行速度很快,没有任何__declspec(noinline)
。正如ElderBug建议的那样,CPU指令缓存存在问题/代码变得太大了吗?
答案 0 :(得分:21)
我把钱花在了英特尔的分支预测器上。 http://en.wikipedia.org/wiki/Branch_predictor
处理器假设(time-cb_last_orbital_update> 5000000)在大多数时间都是假的,并相应地加载执行管道。
一旦条件(时间--cb_last_orbital_update> 5000000)成立。错误的预测延迟会打击你。你可能会松开10到20个周期。
if (time - cb_last_orbital_update > 5000000)
{
cb_last_orbital_update = time;
}
答案 1 :(得分:7)
有些事情正在发生,你没想到。
一个候选者是某些未初始化的变量,它们会在某处运行,具有不同的值,具体取决于您运行的确切代码。例如,您可能有未初始化的内存,有时是非规范化的浮点数,有时则不是。
我认为应该清楚的是,您的代码没有按照您的预期执行。因此,尝试调试代码,在启用所有警告的情况下进行编译,确保使用相同的编译器选项(优化与非优化可轻松成为因子10)。检查您是否得到相同的结果。
特别是当你说“它再次运行得更快时(虽然这并不总是有效,但我看不到一个模式)。还可以将5000000改为5E6一次。它只运行一次但重新编译导致性能再次下降而不会改变任何东西。有一次它只在重新编译两次之后才变慢。“你很可能正在使用不同的编译器选项。
答案 2 :(得分:5)
我会尝试另一种猜测。这是假设的,主要是由于编译器。
我的猜测是你使用了大量的浮点计算,并且在main中引入和使用double值会使编译器用完XMM寄存器(浮点SSE寄存器)。这迫使编译器使用内存而不是寄存器,并在内存和寄存器之间引发大量交换,从而大大降低了性能。这种情况的发生主要是因为计算函数内联,因为函数调用保留了寄存器。
解决方案是将__declspec(noinline)
添加到所有计算函数声明中。
答案 3 :(得分:5)
我建议使用Microsoft Profile Guided Optimizer - 如果编译器对这个特定分支做出错误的假设,它将会有所帮助,并且它很可能也会提高其余代码的速度。
答案 4 :(得分:3)
解决方法,尝试2:
代码现在看起来像这样:
int cb_orbit_update_counter = 1; // before while loop
if(time - cb_orbit_update_counter * 5E6 > 0)
{
cb_orbit_update_counter++;
}
到目前为止,它运行速度很快,加上代码正在被执行时,据我所知。再次只是一种解决方法,但如果这证明可以全部工作那么我很满意。
经过一些更多的测试,似乎很好。
答案 5 :(得分:2)
我的猜测是因为变量cb_last_orbital_update
是只读的,所以当你在if
内分配它时,它会破坏编译器对只读变量的一些优化(例如,它现在可能存储在内存而不是寄存器中)。
要尝试的东西(尽管可能仍然不起作用)是通过cb_last_orbital_update
和time
创建第三个变量,具体取决于条件是否为真,并使用那个。据推测,编译器现在将该变量视为常量,但我不确定。