我正在运行 x86 处理器,但我相信我的问题非常普遍。我很好奇CMP + JE
序列与单MUL
操作消耗的时钟周期的理论差异。
在C伪代码中:
unsigned foo = 1; /* must be 0 or 1 */
unsigned num = 0;
/* Method 1: CMP + JE*/
if(foo == 1){
num = 5;
}
/* Method 2: MUL */
num = foo*5; /* num = 0 if foo = 0 */
不要过分深入研究伪代码,它纯粹是为了阐明这两种方法背后的数学逻辑。
我实际比较的是以下两个指令序列:
方法1:CMP + JE
MOV EAX, 1 ; FOO = 1 here, but can be set to 0
MOV EBX, 0 ; NUM = 0
CMP EAX, 1 ; if(foo == 1)
JE SUCCESS ; enter branch
JMP FINISH ; end program
SUCCESS:
MOV EBX, 5 ; num = 5
FINISH:
方法2:MUL
MOV EAX, 1 ; FOO = 1 here, but can be set to 0
MOV ECX, EAX ; save copy of FOO to ECX
MUL ECX, 5 ; result = foo*5
MOV EBX, ECX ; num = result = foo*5
似乎单个MUL
(总共4个指令)比CMP + JE
(总共6个指令)效率更高,但是指令消耗的时钟周期同样 - - 即完成与任何其他指令相同的指令所需的时钟周期数是多少?
如果消耗的实际时钟周期取决于机器,单个MUL
通常比大多数处理器上的分支方法更快,因为它需要更少的总指令?
答案 0 :(得分:10)
现代CPU性能远远超过计算每条指令的周期数更复杂。您需要考虑以下所有因素(至少):
所有这些都会受到周围代码的严重影响。
基本上,几乎不可能像这样执行微基准并获得有用的结果!
然而,如果我不得不猜测,我会说没有JE的代码通常会更有效,因为它消除了分支,这简化了分支预测行为。
答案 1 :(得分:1)
通常,在现代x86处理器上,CMP
和MUL
指令将占用整数执行单元一个周期(CMP
基本上是SUB
抛弃结果,只修改标志寄存器)。然而,现代x86处理器也是流水线,超标量和无序的,这意味着性能不仅仅取决于这个底层周期成本。
如果无法很好地预测分支,那么分支误预测惩罚将淹没其他因素,MUL
版本将表现得更好。
另一方面,如果分支可以预测和,您可以在后续计算中立即使用num
,那么分支版本就可以了在一般情况下表现更好。那是因为当它正确地预测分支时,它可以在比较结果可用之前使用预测值num
开始推测性地执行下一条指令(而在MUL
情况下,后续使用num
将对MUL
的结果具有数据依赖性 - 在该结果退役之前,它将无法执行。