LEA或ADD指令?

时间:2011-06-12 16:17:41

标签: assembly x86

当我手写汇编时,我通常选择表格

lea eax, [eax+4]

在表格上..

add eax, 4

我听说lea是一个“0时钟”指令(如NOP),而'add'则不是。但是,当我看到编译器生成的程序集时,我经常看到后一种形式而不是第一种。我足够聪明地相信编译器,所以任何人都可以了解哪一个更好?哪一个更快?为什么编译器选择后一种形式呢?

5 个答案:

答案 0 :(得分:52)

x86 CPU上LEAADD之间的一个显着差异是实际执行指令的执行单元。现代的x86 CPU是超标量的,并且有多个并行运行的执行单元,管道供给它们有点像循环(bar stalls)。事实是,LEA由处理寻址的单元(其中一个)处理(在管道的早期阶段发生),而ADD进入ALU(s) /逻辑单元),并在管道的后期。这意味着超标量x86 CPU可以同时执行LEA和算术/逻辑指令。

LEA通过地址生成逻辑而不是算术单元的事实也是它曾经被称为“零时钟”的原因;它没有时间执行,因为地址生成已经发生到它执行时。

它不是 free ,因为地址生成是执行管道中的一个步骤,但它没有执行开销。并且它不占用ALU管道中的一个插槽。

修改:为了澄清,LEA 不是免费的。即使在没有通过算术单元实现它的CPU上,由于指令解码/调度/退出和/或所有指令经过的其他流水线阶段,执行也需要时间。对于通过地址生成实现它的CPU,执行LEA的时间恰好在管道的不同阶段发生

答案 1 :(得分:15)

  

我足够聪明地相信编译器,所以任何人都可以了解哪一个更好?

是的,有点。首先,我从以下消息中获取此信息:https://groups.google.com/group/bsdnt-devel/msg/23a48bb18571b9a6

在这条消息中,开发人员优化了我写得非常糟糕的一些程序集,以便在Intel Core 2处理器中快速运行。作为这个项目的背景,它是我和其他一些开发人员参与的bsd bignum图书馆。

在这种情况下,所有正在优化的是添加两个如下所示的数组:uint64_t* x, uint64_t* y。每个“肢体”或阵列的成员代表bignum的一部分;基本过程是从最不重要的肢体开始迭代它,添加对并继续向上,每次传递进位(任何溢出)。 adc在处理器上为您执行此操作(无法从C中访问进位标志,我不认为)。

在这段代码中,使用lea something, [something+1]jrcxz的组合,这显然比我们之前使用的jnz / add something, size对更有效。但是,我不确定这是否是仅仅测试不同指令的结果。你必须要问。

但是,在稍后的消息中,它是在AMD芯片上测量的,并且表现不佳。

我还了解不同的操作在不同的处理器上执行不同的操作。我知道,例如,GMP项目使用cpuid检测处理器,并根据不同的体系结构传递不同的汇编程序,例如: core2nehalem

您必须问自己的问题是您的编译器是否为您的cpu架构生成优化输出?例如,英特尔编译器就是这样做的,因此可能值得测量性能并查看它产生的输出。

答案 2 :(得分:9)

LEA并不比ADD指令快,执行速度相同。

但是LEA sometimes offer more than ADD。 如果我们需要简单快速的加法/乘法与第二个寄存器相结合,那么LEA可以加速程序执行。 从另一方面来看,LEA不会影响CPU标志,因此没有溢出检测的可能性。

答案 3 :(得分:1)

主要原因是下一个。您可以注意到,如果您仔细看一下x86,则此ISA是两个地址。每个指令最多接受两个参数。因此,操作的语义是下一个:

DST = DST <operation> SRC

LEA是一种hack指令,因为它是x86 ISA中的单指令,实际上是三个地址:

DST = SRC1 <operation> SRC2

这是一种hack指令,因为它重用了x86 CPU的参数分配器电路来执行加法和移位。

编译器之所以使用LEA,是因为在加法寄存器的内容有利于保持不变的情况下,该指令允许它们用一条指令替换少量指令。请注意,在所有情况下,编译器使用LEA DST寄存器都不同于SRC寄存器,或者SRC参数利用复杂的地址计算逻辑。

例如,几乎不可能在生成的代码中找到这样的用例:

LEA EAX, [EAX   ] // equivalent of NOP
LEA EAX, [ECX   ] // equivalent of MOV EAX, ECX
LEA EAX, [EAX+12] // equivalent of ADD EAX, 12

但下一个用例很常见:

LEA EAX, [ECX      +12] // there is no single-instruction equivalent
LEA EAX, [ECX+EDX*4+12] // there is no single-instruction equivalent
LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent

实际上,假设下一个场景是假设应该保留EBP的值以备将来使用:

LEA EAX, [EBP+12]
LEA EDX, [EBP+48]

只需两条指令!但是在缺少LEA的情况下,代码将是下一个

MOV EAX, EBP
MOV EDX, EBP
ADD EAX, 12
ADD EDX, 48

我认为使用LEA的好处现在应该显而易见。您可以尝试替换此说明

LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent

通过基于ADD的代码。

答案 4 :(得分:0)

你可以在同一个时钟周期中执行lea指令,就像添加操作一样,但是如果你使用lea并加在一起,你可以在一个周期中添加三个操作数!如果你要使用两个只能在2个时钟周期内执行的添加操作:

mov eax, [esp+4]   ; get a from stack
mov edx, [esp+8]   ; get b from stack
mov ecx, [esp+12]  ; get c from stack
lea eax, [eax+edx] ; add a and b in the adress decoding/fetch stage of the pipeline
add eax, ecx       ; Add c + eax in the execution stage of the pipeline
ret 12