我偶然发现了一个似乎具有违反直觉性能影响的变化。任何人都可以为这种行为提供可能的解释吗?
原始代码:
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
的计算,所以我没有完全消除它,因为它确实节省了从int
到double
的多次运行时转换的成本。我预计没有任何性能差异,或者如果有的话,可以忽略不计的改进。然而,性能下降了近10%。我已经多次测量过,这确实是我所做的唯一改变。上面显示的代码片段在几个其他循环内执行。我在运行中获得了非常一致的时序,并且可以肯定地确认我所描述的变化会使性能降低约10%。我希望性能会提高,因为int
到double
转换只会在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。我只是想了解我在这里没有考虑的因素。
答案 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个镜头中看到它在快速版本中没有做的事情。您将看到它在汇编语言中停止的细微差别。