gcc -O3使用cmov作为条件,所以它延长了循环携带的依赖链,包括一个cmov(根据Agner Fog’s instruction tables,你的英特尔Sandybridge CPU有2个uop和2个周期的延迟.另见x86标签维基) .这是one of the cases where cmov sucks.
如果数据甚至是中等不可预测的,那么cmov可能是一个胜利,所以这对编译器来说是一个相当明智的选择. (但是,compilers may sometimes use branchless code too much.)
我在put your code on the Godbolt compiler explorer看到asm(有很好的突出显示并过滤掉不相关的行.你仍然需要向下滚动所有的排序代码才能到达main()).
.L82: # the inner loop from gcc -O3
movsx rcx, DWORD PTR [rdx] # sign-extending load of data[c]
mov rsi, rcx
add rcx, rbx # rcx = sum+data[c]
cmp esi, 127
cmovg rbx, rcx # sum = data[c]>127 ? rcx : sum
add rdx, 4 # pointer-increment
cmp r12, rdx
jne .L82
gcc可以通过使用LEA而不是ADD来保存MOV.
ADD-> CMOV(3个周期)的延迟的循环瓶颈,因为循环的一次迭代将rbx写入CMO,并且下一次迭代使用ADD读取rbx.
该循环仅包含8个融合域uop,因此它可以每2个循环发出一次.执行端口压力也不像总和链的延迟那样是一个瓶颈,但它很接近(Sandybridge只有3个ALU端口,不像Haswell的4).
BTW,将