如果语句大大降低了C++的性能,则很少执行且几乎为空

Ken*_*ira 39 c++ performance if-statement

编者的澄清:当最初发布时,有两个问题:

  • 如果添加看似无关紧要的声明,测试性能会下降三倍
  • 完成测试所需的时间似乎随机变化

第二个问题已经解决:只有在调试器下运行时才会出现随机性.

这个问题的其余部分应该被理解为关于上面的第一个要点,并且在VC++ 2010 Express的发布模式下运行,优化"最大化速度"和"支持快速代码".

评论部分仍然有一些评论谈论第二点,但现在可以忽略它们.


我有一个模拟,如果我在运行实际模拟的while循环中添加一个简单的if语句,性能下降大约三倍(我在while循环中运行了很多计算,n-body引力用于太阳能系统除了其他东西)即使if语句几乎从未执行过:

if (time - cb_last_orbital_update > 5000000)
{
    cb_last_orbital_update = time;
}
Run Code Online (Sandbox Code Playgroud)

with timecb_last_orbital_update同时属于类型double并在main函数的开头定义,其中if语句也是如此.通常我也想在那里运行计算,但如果我删除它们没有任何区别.上面的if语句对性能有相同的影响.

变量time是模拟时间,它在开始时增加0.001步,因此在第一次执行if语句之前需要很长时间(我还包括打印消息以查看它是否正在执行,但它是不,或者至少只在它应该的时候).无论如何,即使在模拟的最初几分钟内尚未执行一次,性能也会下降3倍.如果我注释掉这条线

cb_last_orbital_update = time;
Run Code Online (Sandbox Code Playgroud)

然后它再次运行得更快,所以这不是检查

time - cb_last_orbital_update > 5000000
Run Code Online (Sandbox Code Playgroud)

或者,这绝对是将当前模拟时间写入此变量的简单行为.

此外,如果我将当前时间写入另一个变量而不是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;
    }
    }
...
}
Run Code Online (Sandbox Code Playgroud)

编辑2:

是汇编代码的不同之处.左边是带线的快速代码

cb_last_orbital_update = time;
Run Code Online (Sandbox Code Playgroud)

取消注释,右边是慢线代码.

编辑4:

所以,我发现到目前为止似乎工作正常的解决方法:

int cb_orbit_update_counter = 1; // before while loop

if(time - cb_orbit_update_counter * 5E6 > 0)
{
    cb_orbit_update_counter++;
}
Run Code Online (Sandbox Code Playgroud)

编辑5:

虽然该解决方法确实有效,但它只能与使用相结合__declspec(noinline).我刚刚再次从函数声明中删除了那些,以查看它是否会发生任何变化,而且确实如此.

编辑6:对不起,这让人感到困惑.我在追踪__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");
    }
}

}
Run Code Online (Sandbox Code Playgroud)

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++;
}
Run Code Online (Sandbox Code Playgroud)

我单独__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]);
}
}
Run Code Online (Sandbox Code Playgroud)

并使代码的另一部分更加苗条.该程序现在运行速度快,没​​有任何__declspec(noinline).正如ElderBug建议的那样,CPU指令缓存问题/代码变得太大了?

Mar*_*ann 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 它也可能是一种罕见的分支预测混叠症状.他的一个子程序可以映射到分支预测逻辑中的相同条目,因此分支预测可以基于两个不同指令存储器位置处的两个不同分支来回抖动.摆弄代码将偏移分支的位置并导致使用不同的分支预测条目,因此不会导致分支预测捶打. (13认同)
  • 没错,但每5,000,000/0.001次执行只会发生一次.所以可能没什么大不了的. (4认同)
  • 嗯,但这并不意味着它应该只在那些罕见的情况下变得更慢吗?性能下降始终存在,即使它甚至没有执行过一次. (3认同)
  • @dau_sama:好的,所以我玩了一下,现在正在使用__assume(cb_last_orbital_update == 0);. 我之前可能只是错了.我仍然不明白这是一个什么问题,但我很高兴现在至少有一个解决方法,谢谢:) (3认同)
  • 试试这个 https://msdn.microsoft.com/en-us/library/1b3fsfxw%28VS.80%29.aspx 在 gcc 中我可能/不太可能使用,快速搜索显示 msvc 中的对应是 __assume。它会提示编译器某个条件不太可能为真/假。我很好奇结果!在某些情况下,在 gcc 上它会产生很大的差异 (2认同)

gna*_*729 7

有些事情正在发生,你没想到.

一个候选者是一些悬浮在某处的未初始化变量,它们具有不同的值,具体取决于您运行的确切代码.例如,您可能有未初始化的内存,有时是非规范化的浮点数,有时则不是.

我认为应该清楚的是,您的代码没有按照您的预期执行.因此,尝试调试代码,在启用所有警告的情况下进行编译,确保使用相同的编译器选项(优化与非优化可轻松成为因子10).检查您是否得到相同的结果.

特别是当你说"它再次运行得更快时(虽然这并不总是有效,但是我看不到模式).还可以将5000000改为5E6一次.它只运行一次但重新编译导致性能下降再一次没有改变任何东西.有一次它只在重新编译两次之后才变慢." 你很可能正在使用不同的编译器选项.

  • @Kenira我认为你的意思是*IDE*,而不是*编译器*.编译器是一个将代码转换为可执行文件的程序.编译器不运行可执行文件. (3认同)

Eld*_*Bug 5

我会尝试另一种猜测.这是假设的,主要是由于编译器.

我的猜测是你使用了很多浮点计算,并且在main中引入和使用double值会使编译器用完XMM寄存器(浮点SSE寄存器).这迫使编译器使用内存而不是寄存器,并在内存和寄存器之间引发大量交换,从而大大降低了性能.发生这种情况主要是因为计算函数内联,因为函数调用是保留寄存器.

解决方案是添加__declspec(noinline)所有计算函数声明.

  • @Kenira我没有想法.也许您可以检查生成的程序集,并发布快速和慢速代码之间的差异? (3认同)

for*_*cve 5

我建议使用Microsoft Profile Guided Optimizer - 如果编译器对这个特定的分支做出错误的假设,它将有所帮助,并且它很可能也会提高其余代码的速度.