2012-04-17 56 views
2

如果您有一个变量A,永远不会变化并始终等于零,函数F,函数G和函数H,并在现代英特尔台式机处理器上调用以下代码在现代版本的GCC上-O3优化:无法解释C++性能

for(i = 0; i < a_big_number; i++) 
{ 
if(A != 0) F(); 
else G(); 
} 

需要2秒钟执行。注意F永远不会被调用,因为A总是0.或者,

for(i = 0; i < a_big_number; i++) 
{ 
if(A != 0) H(); 
else G(); 
} 

只需要1秒钟执行。再次,A总是0,​​而H从来不被调用。最后,

for(i = 0; i < a_big_number; i++) 
{ 
G(); 
} 

只需要0.5秒执行。

鉴于前两个例子中的条件陈述,为什么F和H的内容是什么?既然他们从来没有被调用过,为什么它们会影响他们的工作?鉴于英特尔处理器具有复杂的分支预测,处理器不应该知道G()总是被调用,甚至不会浪费时间在条件语句上?我明白有条件的教学应该浪费一些时间,但我不明白为什么浪费那么多时间。

+14

请提供一个完整,简单,可编译的独立示例,演示相对性能。 (另外,请注意,几秒或更短时间内运行的基准很可能会产生非常嘈杂的结果,并且请注意,找出性能差异的原因的最佳方法通常是查看生成的程序集。 ) – 2012-04-17 20:55:09

+0

为什么比较语句需要零时间?如果GCC没有足够的信息对其进行优化,则需要执行CPU周期。 – dasblinkenlight 2012-04-17 20:56:34

+2

F,G或H中的任何一个是否声明为“inline”?是一个声明的'const'?这三个代码片段是否出现在一个'main'中?关于后者:缓存可能解释一些性能差异;尝试重新排序三个代码块。 – 2012-04-17 20:58:22

回答

1

假设编译器明白A是一个常数,它应该把这个代码:

for(i = 0; i < a_big_number; i++) 
{ 
    if(A != 0) F(); 
    else G(); 
} 

成这样:

if(A != 0) 
    for(i = 0; i < a_big_number; ++i) 
     F(); 
else 
    for(i = 0; i < a_big_number; ++i) 
     G(); 

或完全优化了F()函数调用,如果一个恒定的似乎是编译时常量。

如果没有发生这种情况(即有可能的副作用或其他 - 编译器不能保证你如何以及如何优化),循环会遇到来自分支错误预测的性能问题。如果A没有改变并且被调用函数足够小,CPU应该锁定循环并记住分支,所以它不会一遍又一遍地重复相同的错误。另一方面,循环可能会被展开,并且这可能会受到很大的伤害,因为它不能被并行化,只会影响代码的大小以及CPU必须跟踪的一些事情。

你如何衡量执行时间对我来说是个谜,以及你在循环中调用的那个函数在做什么。例如,您可以测量流程执行交换。因此,除非您提供完整的实例并详细说明您的测量方法,否则不可能告诉您发生了什么事情。

无论如何,我敢打赌你的时间测量是不正确的,或者你在没有显示的代码中做了一些坏事,或者上述所有内容。

+2

+1对于您实际测量时间的观点。不幸的是,测量时间的方式实际上并不能衡量你想要的结果。更不幸的是,那些没有做过这么多事情的人倾向于完全相信他们测量时间的方法不能成为问题。 :( – Hurkyl 2012-04-17 22:32:30

0

就我所知,编译器无法确定分支是否不会执行。编译器可以做的最好的事情是预测哪个分支更有可能。

+0

编译器不能,但CPU具有很好的分支预测机制,更不用说编译器实际上有基于配置文件的优化和提示分支预测来重新安排生成的代码。 – 2012-04-18 01:42:04