Assembly LEA还是ADD指令?

Assembly LEA还是ADD指令?,assembly,x86,Assembly,X86,当我进行手写汇编时,我通常选择表格 lea eax, [eax+4] 在表格上 add eax, 4 我听说lea是一个“0时钟”指令(像NOP),而add不是。然而,当我查看编译器生成的程序集时,我经常看到使用后一种形式,而不是第一种形式。我很聪明,可以信任编译器,所以谁能告诉我哪一个更好?哪一个更快?为什么编译器选择后者而不是前者 我很聪明,可以信任编译器,所以谁能告诉我哪一个更好 是的,有一点。首先,我从以下信息中得出这一结论: 在本文中,一位开发人员优化了一些我写得非常糟糕的程序集,

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

lea eax, [eax+4]
在表格上

add eax, 4
我听说lea是一个“0时钟”指令(像NOP),而add不是。然而,当我查看编译器生成的程序集时,我经常看到使用后一种形式,而不是第一种形式。我很聪明,可以信任编译器,所以谁能告诉我哪一个更好?哪一个更快?为什么编译器选择后者而不是前者

我很聪明,可以信任编译器,所以谁能告诉我哪一个更好

是的,有一点。首先,我从以下信息中得出这一结论:

在本文中,一位开发人员优化了一些我写得非常糟糕的程序集,使其在Intel Core 2处理器中快速运行。作为这个项目的背景,它是一个bsd bignum库,我和其他一些开发人员都参与了这个库

在本例中,所要优化的只是添加两个如下所示的阵列:
uint64\u t*x,uint64\u t*y
。数组的每个“肢体”或成员表示bignum的一部分;基本过程是从最不重要的分支开始对其进行迭代,将该对相加并继续向上,每次向上传递进位(任何溢出)<代码>adc在处理器上为您执行此操作(我认为不可能从C访问进位标志)

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

然而,在随后的消息中,它是在AMD芯片上测量的,性能不太好

我还了解不同的操作在不同的处理器上执行的不同。例如,我知道GMP项目使用
cpuid
检测处理器,并根据不同的体系结构传入不同的组装例程,例如
core2
nehalem


您必须问自己的问题是,编译器是否为您的cpu体系结构生成优化的输出?例如,众所周知,英特尔编译器可以做到这一点,因此可能值得测量性能并查看它产生的输出。

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

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

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

LEA
通过地址生成逻辑而不是算术单元,这也是它过去被称为“零时钟”的原因;它不需要时间执行,因为地址生成在执行时已经发生了

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


编辑:为了澄清,
LEA
不是免费的。即使在不通过算术单元实现它的CPU上,由于所有指令都经过指令解码/分派/失效和/或其他流水线阶段,执行也需要时间。对于通过地址生成实现LEA的CPU来说,执行LEA所需的时间只发生在管道的不同阶段。

您可以像加法操作一样在相同的时钟周期内执行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

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

DST = DST <operation> SRC
但接下来的用例很常见:

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

基于添加的代码

如果任何指令实际上在做有用的工作,它怎么可能是“零时钟”指令?它是零时钟指令,因为所有需要的工作都是在解码步骤中完成的-当CPU解码指令时,偏移量无论如何都是从MODRM/SIB计算出来的。至少这是我的理论。另外,我确切地知道lea指令是什么以及它的作用是什么——我的问题是关于lea与add,而不是lea与mov(这是一个巨大的区别——在没有访问内存的情况下,你不能在“mov”中使用置换)。很久以前,回到最初的奔腾,这是真的。现代编译器为以后的内核生成代码。手工优化机器代码的时代已经结束了。它过去很便宜(不是免费的),因为它在旧芯片上使用专用的地址计算硬件,并为您带来了一些并行性。在当前的CPU上,两条指令都可能
MOV EAX, EBP
MOV EDX, EBP
ADD EAX, 12
ADD EDX, 48
LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent