我正在阅读this article,我注意到了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 :(得分:2)
假设我们有cmp a,b
。
处理器将临时减去操作数(不影响其值),正确设置标志,然后评估jmp
跳转操作。
因此,在您的示例中,制作jz
,而不是cmp
和jmp
的速度更快。
答案 1 :(得分:0)
我认为这主要是solved problem与现代编译器。或者,如果没有解决,那么至少通常正确,应用启发式方法来产生更好的循环变换。对于x86 [-64],有更重要的考虑因素,例如,循环&#39; body&#39;适合指令缓存?跳跃目的地是否适当对齐?分支预测是否有效?
使用x86实现循环有多种方法。例如,
使用j[e|r]cxz
指令,避免标记寄存器 - 虽然解码速度较慢。
在sub
之前使用add
或j<cond>
而不是dec
或inc
,以避免部分标记寄存器停止。
operation
也很重要。编译时是否知道max
?
for (int i = 0; i < max / 4; i++)
{
operation;
operation;
operation;
operation;
}
如果operation
足够简单(例如,浮点运算),它可能会从这种n路调度中受益。如果每个操作都取决于之前的操作,则不会。
查看GMP之类的内容。 mpn/x86[-64]
目录为不同的微架构优化循环。 Agner Fog's optimization manuals是一个很好的资源。
答案 2 :(得分: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
,以保存一个寄存器。