4

我在读this article,我注意到了jz指令。这让我思考:“Jump if zero”(jz)更快吗?

请问这代码组件,以票面价值

for (int i=max;i!=0;--i){ 
    //Some operation 
} 

采取超越这个代码?

for (int i=0;i<max;++i){ 
    //Some operation 
} 

只要你不关心你的数据与增加i处理,不存在语义差别。缓存未命中也不会受到影响,因为它们可以按顺序工作。

我在汇编时不够好写例子,但我会认为第一个例子只会使用jz。第二个将使用cmp,然后使用jg,并且还需要另一个变量max。第一个例子只需要循环计数器,因为0是隐含的。

这也可能是编译器用来优化的东西,但我可以想象它无法进行优化的情况。

+0

是的,我认为这取决于每个不同跳转指令所需的周期数。我不知道离手,但是如果你查看你的目标指令集,你应该能够找到每条指令使用的循环次数的枚举。 – JustKevin 2014-09-05 04:48:47

+0

它可能仍然需要cmp指令,因为它是存储位置的比较。循环计数器也可以在稍后的编译器中自动分配给寄存器。 – JustKevin 2014-09-05 04:52:02

+0

另请参阅:http://stackoverflow.com/a/22466475 – harold 2014-09-06 09:50:21

回答

2

假设我们有cmp a,b

处理器将暂时减去操作数(不影响它们的值),正确设置标志,并在此之后评估跳转操作。

因此,例如制作jz,而不是cmpjmp的速度会更快。

+0

我忘了减量将设置零标志。好点子。 – JustKevin 2014-09-05 14:29:57

0

我认为这主要与现代编译器solved problem。或者,如果不是解决了,那么至少通常正确,应用启发式来产生更好的循环变换。对于x86 [-64],还有更重要的考虑因素,例如循环体是否适合指令缓存?跳转目标是否合适对齐?分支预测是否有效?

有多种方法可以用x86实现循环。例如,

  • 使用j[e|r]cxz指令,避免标志寄存器 - 尽管慢于解码。

  • 使用subadd之前j<cond>而非decinc,以避免局部的标志寄存器摊位。

operation也很重要。编译时已知是max

for (int i = 0; i < max/4; i++) 
{ 
    operation; 
    operation; 
    operation; 
    operation; 
} 

operation如果足够简单(例如,浮点运算),它可能受益于这种n路的调度。如果每个操作都依赖于以前的操作,则不会。

看一看像GMPmpn/x86[-64]目录为不同的微架构优化循环。 Agner Fog's optimization manuals是一个很好的资源。

0

有时,循环倒计数可以释放寄存器,这可能会比跳转条件中的任何差异更有用(特别是在x86-32上,寄存器很少)。

但是,在循环中倒数可以防止自动矢量化。如果你的计算可以用SSE/AVX /做什么的,你可能需要编写代码就像

for (i=end; i;){ 
    i-=4; 
    val[i+0] = foo(i); 
    val[i+1] = foo(i); 
    val[i+2] = foo(i); 
    val[i+3] = foo(i); 
    } 

,然后你必须确保end%4 == 0,都保存一个寄存器。