x86汇编语言习惯用法

17

我一直在努力掌握x86汇编语言,并想知道是否有一个类似于movl $1, %eax的简短等效方式。这时,我想到了频繁使用的习语列表可能是一个好主意。

这可能包括首选使用xorl %eax,%eax而不是movl $0,%eax,或者testl %eax,%eaxcmpl $0,%eax的比较。

哦,每个帖子请友好地发布一个例子!


5
movl $1, %eax 很快且简短。在某些处理器上,xorl %eax, %eax 实际上比 movl $0, %eax 更慢。在其他处理器上,incl %eaxaddl $1, %eax 更慢。如果您在2010年编写汇编代码,应该知道您正在为哪种架构编写并相应地选择您的“方言”(为了保持与语言的比喻)。 - Pascal Cuoq
@Pascal Cuoq,您能否解释一下影响这种性能差异的因素是什么?我特别困惑于incl %eaxaddl $1, %eax慢。此外,如果您能指向一些详细说明这种行为的链接,我将不胜感激! - susmits
对于2010年的所有x86架构,xor eax,eax更快或等效,在任何情况下都更短。请查看https://dev59.com/HXM_5IYBdhLWcg3wZSM6#1396552。这几乎是从486时代开始的。 - Gunther Piez
投票关闭,因为问题过于宽泛。已经有其他帖子提到了所提及的个别示例。 - Ciro Santilli OurBigBook.com
10个回答

13

这里有一个很有趣的“习语”。希望每个人都知道,与乘法相比,除法需要更多的时间。使用一些数学知识,可以通过乘以常数的倒数来代替除法。这超出了 SHR 技巧。例如,要除以 5:

mov eax, some_number
mov ebx, 3435973837    // 32-bit inverse of 5
mul ebx
现在,通过不使用慢除法指令,eax已被除以5。以下是从http://blogs.msdn.com/devdev/archive/2005/12/12/502980.aspx中窃取的有用除数常量列表。
3   2863311531
5   3435973837
7   3067833783
9   954437177
11  3123612579
13  3303820997
15  4008636143
17  4042322161

对于列表中没有的数字,您可能需要在进行操作之前进行偏移(除以6,shr 1,然后乘以3的倒数)。


7

在x64上:

xor eax, eax 

对于

xor rax, rax

第一个指令也隐式地清除了 rax 的上半部分,但具有较小的操作码。


7

使用LEA进行乘法运算,例如:

lea eax, [ecx+ecx*4]   

对于 EAX = 5 * ECX


5
顺便说一下,在NetBurst上这个速度极慢,因为英特尔为了能够获得更高的时钟速度而删除了移位寄存器。具有讽刺意味的是,当P4问世时,这仍然在英特尔的优化手册中有所记录。 - Jörg W Mittag
感谢您对速度的评论。我意识到习语并不一定等同于优化。然而,作为一种惯用语,我认为LEA已经被相当广泛地(滥)使用了。 - PhiS
5
这是一种优化方式,并且甚至被英特尔官方推荐。只是在正式推荐了15年后,他们突然发布了一款运行该优化方式表现缓慢的新CPU,因此基本上需要重新编译曾经编写过的每个程序。值得庆幸的是,NetBurst很快就消亡了,当前所有微架构都是Pentium III的进化版本,而不是Pentium 4,所以所有现有的CPU都再次拥有Barrel Shifter。基本上,自80385以来,所有英特尔CPU和Athlon都具备它,只有Pentium4没有。 - Jörg W Mittag

5

你可能会问如何在汇编中进行优化。然后你必须问自己,你要优化什么:大小还是速度?无论如何,这是我的 "惯用语",可以替换 xchg

xor eax, ebx
xor ebx, eax
xor eax, ebx

警告:如果eax == ebx,则两者都将被清零! - LiraNuna
12
你确定吗?42的42次方等于0;42的0次方等于42;0的42次方等于42。 - Sparafusile

5

扩展我的评论:

对于像 Pentium Pro 这样的不加区分的处理器,xorl %eax,%eax 看起来似乎依赖于 %eax,因此必须等待该寄存器的值可用。后来的处理器实际上有额外的逻辑来识别那个指令没有任何依赖。

incldecl 指令设置一些标志但保持其他标志不变。如果将标志建模为指令重新排序的单个寄存器,则情况最差:在 incldecl 之后读取标志的任何指令都必须被视为依赖于 incldecl(如果它正在读取该指令设置的标志之一),并且还依赖于先前设置标志的指令(如果它正在读取该指令未设置的标志之一)。解决方案是将标志寄存器分成两个,并考虑到这种更细粒度的依赖关系……但 AMD 有一个更好的想法,并从他们几年前提出的 64 位扩展中完全删除了这些指令。

关于链接,我在英特尔手册中发现了这个链接,但提供链接是无用的,因为它们在每六个月重新组织一次公司网站,或者在 Agner Fog 的网站上:http://www.agner.org/optimize/#manuals


5

在循环中...

  dec     ecx 
  cmp     ecx, -1       
  jnz     Loop              

is

  dec     ecx  
  jns     Loop 

更快更简洁。

循环不是更简单吗? - Hasan Saad
1
@Hasan Saad:虽然可以使用循环,但它速度较慢,在x86中使用循环已经过时了。 - GJ.
我之前不知道这个,非常感谢你提供的信息。非常感激 :) - Hasan Saad

3

使用SHLSHR进行2的幂次方乘法/除法


它也可以扩展到其他数字。例如,y*320 = (y << 8) + (y << 6)。但这并不总是比简单的乘法更快,这取决于你的处理器。 - csl

2

除了 xor 之外,还有另一种方法可以实现这个功能。

mov eax, 0   ; B800000000h

is

sub eax, eax ; 29C0h

理由:更小的操作码


2

不确定这是否算成语,但在i7之前的大多数处理器上

movq xmm0, [eax]
movhps xmm0, [eax+8]

或者,如果SSE3可用,

lddqu xmm0, [eax]

相较于从未对齐的内存位置读取,它们更快。

movdqu xmm0, [eax]

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接