2

如果您有一个永远不会改变且始终为零的变量 A、一个函数 F、一个函数 G 和一个函数 H,并在现代版 GCC 上使用 -O3 优化的现代英特尔台式机处理器上调用以下代码:

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 的内容很重要?既然他们从来没有被召唤过,为什么他们所做的事情会有所不同呢?并且鉴于 Intel 处理器具有复杂的分支预测,处理器难道不应该弄清楚 G() 总是被调用,甚至永远不会在条件语句上浪费时间吗?我知道条件指令应该浪费一些时间,但我不明白为什么它会浪费这么多时间。

4

2 回答 2

0

假设编译器理解这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 必须跟踪的许多事情。

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

无论如何,我敢打赌,您的时间测量不正确,或者您在未显示的代码中做了坏事,或者以上所有情况。

于 2012-04-17T21:10:33.450 回答
0

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

于 2012-04-18T01:06:48.897 回答