问题描述
|
当我进行手写组装时,通常选择表格
lea eax,[eax+4]
表格上方
add eax,4
我听说lea是\“ 0-clock \”指令(如nop),而\'add \'不是。但是,当我看编译器生成的Assembly时,我经常看到使用后一种形式而不是第一种形式。我足够聪明,可以信任编译器,因此任何人都可以阐明哪个更好吗?哪一个更快?为何编译器选择前者而不是后者?
解决方法
在x86 CPU上,
LEA
和ADD
之间的一个重要区别是实际执行指令的执行单元。现代的x86 CPU是超标量的,并且具有多个并行运行的执行单元,而管线则像轮循机制(bar停顿)那样向它们供电。事实是,LEA
由处理寻址的单元之一(在流水线的早期发生),而ADD
则由ALU(算术/逻辑单元)处理,后期管道。这意味着一个超标量x86 CPU可以同时执行一个“ 2”和一个算术/逻辑指令。
“ 2”通过地址生成逻辑而不是算术单元的事实也是为什么它被称为“零时钟”的原因。它不需要花时间执行,因为地址生成已经在执行之前就已经发生。
它不是免费的,因为地址生成是执行管道中的一个步骤,但是没有执行开销。并且它不占用ALU管道中的插槽。
编辑:澄清一下,ѭ2不是免费的。即使在未通过算术单元实现该功能的CPU上,由于指令解码/分派/退出和/或所有指令均经过的其他流水线阶段,执行时间仍然很长。对于通过地址生成实现该功能的CPU,执行“ 2”所需的时间仅发生在流水线的不同阶段。
,
我足够聪明,可以信任编译器,因此任何人都可以阐明哪个更好吗?
对,一点儿。首先,我从以下消息中获取此信息:https://groups.google.com/group/bsdnt-devel/msg/23a48bb18571b9a6
在此消息中,开发人员优化了一些我写得很糟糕的程序集,以便在Intel Core 2处理器中疯狂地快速运行。作为该项目的背景,它是我和其他一些开发人员都参与的bsd bignum库。
在这种情况下,所有要优化的就是添加两个看起来像这样的数组:“ 10”。每个\“ limb \”或数组的成员代表bignum的一部分;基本过程是从最低有效的肢体开始对其进行迭代,将对加起来并继续向上,每次向上传递进位(任何溢出)。 adc
在处理器上为您完成此操作(我认为无法从C访问进位标志)。
在那段代码中,使用了lea something,[something+1]
和jrcxz
的组合,显然比我们以前可能使用的jnz
/add something,size
对更有效。但是,我不确定是否是通过简单地测试不同的指令而发现的。您必须要问。
但是,在以后的消息中,它是在AMD芯片上进行测量的,效果并不理想。
我还将了解不同的操作在不同处理器上的执行情况不同。我知道,例如,GMP项目使用cpuid
检测处理器,并根据不同的架构传入不同的组装例程,例如core2
,nehalem
。
您必须问自己的问题是,编译器是否会为您的cpu架构生成优化的输出?例如,众所周知,英特尔编译器可以做到这一点,因此可能值得评估性能并查看其产生的输出。
, LEA不比ADD指令快,执行速度相同。
但是,LEA有时会提供比添加更多的功能。
如果我们需要简单快速的加/乘运算与第二个寄存器相结合,那么LEA可以加快程序的执行速度。
另一方面,LEA不会影响CPU标志,因此没有溢出检测的可能性。
,主要原因是下一个。您可以注意到,如果您仔细看一下x86,则此ISA是两个地址。每个指令最多接受两个参数。因此,操作的语义是下一个:
DST = DST <operation> SRC
LEA是一种hack指令,因为它是x86 ISA中的SINGLE指令,实际上是三个地址:
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的代码。
, 您可以像添加操作一样在同一时钟周期内执行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