奇怪的C ++性能差异?

时间:2010-02-05 18:59:53

标签: c++ optimization performance

我偶然发现了一个似乎具有违反直觉性能影响的变化。任何人都可以为这种行为提供可能的解释吗?

原始代码:

for (int i = 0; i < ct; ++i) {
    // do some stuff...

    int iFreq = getFreq(i);
    double dFreq = iFreq;

    if (iFreq != 0) {
        // do some stuff with iFreq...
        // do some calculations with dFreq...
    }
}

在“性能传递”期间清理此代码时,我决定在dFreq块中移动if的定义,因为它仅在if内使用。有几个涉及dFreq的计算,所以我没有完全消除它,因为它确实节省了从intdouble的多次运行时转换的成本。我预计没有任何性能差异,或者如果有的话,可以忽略不计的改进。然而,性能下降了近10%。我已经多次测量过,这确实是我所做的唯一改变。上面显示的代码片段在几个其他循环内执行。我在运行中获得了非常一致的时序,并且可以肯定地确认我所描述的变化会使性能降低约10%。我希望性能会提高,因为intdouble转换只会在iFreq != 0时发生。

抄袭代码:

for (int i = 0; i < ct; ++i) {
    // do some stuff...

    int iFreq = getFreq(i);

    if (iFreq != 0) {
        // do some stuff with iFreq...
        double dFreq = iFreq;
        // do some stuff with dFreq...
    }
}

任何人都能解释一下吗?我正在使用带有/ O2的VC ++ 9.0。我只是想了解我在这里没有考虑的因素。

8 个答案:

答案 0 :(得分:7)

在使用iFreq进行计算之前,您应该在if()之前立即将转换转换为dFreq。如果指令在代码中更远,则转换可以与整数计算并行执行。一个好的编译器可能能够将它推得更远,而一个不那么好的编译器可能会把它放在它落到的地方。由于您将其移动到整数计算之后,它可能无法与整数代码并行运行,从而导致速度减慢。如果它确实并行运行,那么取决于CPU(发出FP指令,其结果从未使用过,对原始版本影响不大)可能几乎没有任何改进。

如果你真的想要提高性能,许多人已经做了基准测试并按以下顺序对以下编译器进行排名:

1)ICC - 英特尔编译器 2)海湾合作委员会 - 一个很好的第二名 3)MSVC生成的代码与其他代码相比可能非常差。

如果他们拥有它,您可能还想尝试-O3。

答案 1 :(得分:6)

在第一种情况下,getFreq的结果可能会保留在寄存器中并在第二种情况下写入内存?也可能是,性能下降与CPU机制有关,如流水线和/或分支预测。 您可以检查生成的汇编代码。

答案 2 :(得分:4)

这对我来说就像一个管道摊位

int iFreq = getFreq(i);
    double dFreq = iFreq;

    if (iFreq != 0) {

允许转换为与其他代码并行发生的 因为dFreq没有立即使用。它给编译器一些东西 在存储iFreq和使用它之间做,所以这种转换很可能 “自由”。

但是

int iFreq = getFreq(i);

if (iFreq != 0) {
    // do some stuff with iFreq...
    double dFreq = iFreq;
    // do some stuff with dFreq...
}

自从你开始使用double值后,可能会在转换为double后触及商店/参考档位。

现代处理器可以在每个时钟周期执行多项操作,但仅限于事物是独立的。引用相同寄存器的两个连续指令通常会导致停顿。实际转换为double可能需要3个时钟,但除了第一个时钟之外的所有时钟都可以与其他工作并行完成,前提是您没有参考指令或两个指令的转换结果。

C ++编译器在重新排序指令以获得利用这一点时非常擅长,看起来你的更改击败了一些不错的优化。

另一种(不太可能)的可能性是,当转换为float在分支之前时,编译器能够完全删除分支。无分支代码通常是现代处理器中的主要性能胜利。

看看编译器为这两种情况实际发出的指令会很有趣。

答案 3 :(得分:3)

尝试在for循环之外移动dFreq的定义,但将赋值保留在for循环/ if块中。

也许在if中,每个for循环在堆栈上创建dFreq都会导致问题(虽然编译器应该处理这个问题)。也许是编译器中的回归,如果dFreq var在它创建的四个循环中,则在if内部为每次创建它。

double dFreq;
int iFreq;
for (int i = 0; i < ct; ++i) 
{
    // do some stuff...

    iFreq = getFreq(i);

    if (iFreq != 0) 
    {
        // do some stuff with iFreq...
        dFreq = iFreq;
        // do some stuff with dFreq...
    }
} 

答案 4 :(得分:2)

也许编译器正在优化它,在for循环之外定义。当你把它放在那里时,如果编译器优化没有这样做。

答案 5 :(得分:1)

这种变化有可能导致编译器禁用某些优化。如果将声明移到循环上方会发生什么?

答案 6 :(得分:1)

一旦我阅读了一篇关于优化的文档,该文档表示在使用之前定义变量,甚至之前都不是一个好的做法,编译器可以根据该建议优化代码。

这篇文章(有点陈旧但非常有效)说(有统计数据)类似的东西:http://www.tantalon.com/pete/cppopt/asyougo.htm#PostponeVariableDeclaration

答案 7 :(得分:1)

很容易找到答案。只需要20 stackshots的慢速版本和快速版本。在慢速版本中,您将在大约2个镜头中看到它在快速版本中没有做的事情。您将看到它在汇编语言中停止的细微差别。