考虑轻度代码高尔夫
收缩
而不是扩展代码
,尤其是在循环之前。例如
xor eax,eax
/
cdq
如果需要两个调零寄存器,或
mov eax, 1
/
lea ecx, [rax+1]
将寄存器设置为1和2,总字节数仅为8,而不是10。看见
Set all bits in CPU register to 1 efficiently
了解更多信息
Tips for golfing in x86/x64 machine code
了解更一般的想法。不过,您可能仍然希望避免错误的依赖关系。
或通过以下方式填充额外空间
creating a vector constant on the fly
而不是从内存中加载。(不过,对于包含设置+内部循环的较大循环,增加更多的uop缓存压力可能会更糟。但它可以避免常量的d-cache未命中,因此可以弥补运行更多uop的不足。)
如果您还没有使用它们加载“压缩”常量,
pmovsxbd
,
movddup
或
vpbroadcastd
长度超过
movaps
. dword/qword广播加载是免费的(没有ALU uop,只有加载)。
如果您担心代码对齐,那么您可能会担心它在L1I缓存中的位置,或者uop缓存边界的位置,因此仅仅计算总uop已经不够了,块中还有一些额外的uop
之前
你关心的人可能根本不是问题。
但在某些情况下,您可能真的希望在对齐块之前优化指令的解码吞吐量/uop缓存使用率/总uop。
填充说明,如要求的问题:
Agner Fog对此有一整节:“10.6为了对齐而延长说明”
在his中
"Optimizing subroutines in assembly language" guide
. (The
lea
,
push r/m64
,SIB的想法来自那里,我抄了一两句话/短语,否则这个答案就是我自己的作品,要么是不同的想法,要么是在查看Agner指南之前写的。)
当前CPU尚未更新,但:
lea eax, [rbx + dword 0]
比过去有更多的缺点
mov eax, ebx
,因为你错过了
zero-latency / no execution unit
mov
. 如果它不在关键路径上,尽管去做。易于理解的
lea公司
具有相当好的吞吐量,并且具有较大寻址模式(甚至可能有一些段前缀)的LEA在解码/执行吞吐量方面可能比
压敏电阻
+
nop
.
使用一般形式,而不是简短形式(无ModR/M),如
push reg
或
mov reg,imm
. e、 g.使用2字节
推动r/m64
对于
push rbx
. 或者使用更长的等效指令,如
add dst, 1
而不是
inc dst
,
in cases where there are no perf downsides to
inc
所以你已经在使用
股份有限公司
.
使用SIB字节
. 您可以让NASM通过使用单个寄存器作为索引来实现这一点,如
mov eax, [nosplit rbx*1]
(
see also
),但与简单编码相比,这会影响负载使用延迟
mov eax, [rbx]
具有SIB字节。索引寻址模式对SnB系列还有其他缺点,
like un-lamination and not using port7 for stores
.
所以
最好只是编码
base=rbx + disp0/8/32=0
使用ModR/M+SIB,无索引reg
. (表示“无索引”的SIB编码是表示idx=RSP的编码)。
[rsp + x]
寻址模式已经需要SIB(base=RSP是转义代码,意味着有SIB),并且始终出现在编译器生成的代码中。因此,现在和将来都有充分的理由期望它能够完全高效地解码和执行(即使对于RSP以外的基址寄存器也是如此)。NASM语法无法表达这一点,因此必须手动编码。GNU gas Intel语法来自
objdump -d
说
8b 04 23 mov eax,DWORD PTR [rbx+riz*1]
对于Agner Fog的示例10.20。(
riz
是一个虚构的索引零表示法,意味着有一个没有索引的SIB)。我还没有测试GAS是否接受它作为输入。
使用
imm32
和/或
disp32
只需要
imm8
或
disp0/disp32
.
Agner Fog对Sandybridge uop缓存的测试(
microarch guide table 9.1
)指示重要的是立即数/位移的实际值,而不是指令编码中使用的字节数。我没有关于Ryzen uop缓存的任何信息。
So NASM
imul eax, [dword 4 + rdi], strict dword 13
(10字节:操作码+modrm+disp32+imm32)将使用32small、32small类别,并在uop缓存中获取1个条目,这与立即数或disp32实际具有超过16个有效位的情况不同。(然后需要2个条目,从uop缓存加载它需要额外的周期。)
根据阿格纳的表格,8/16/32小码总是等同于SnB。寄存器的寻址模式是相同的,无论是完全没有位移,还是很小,所以
mov dword [dword 0 + rdi], 123456
需要2个条目,就像
mov dword [rdi], 123456789
. 我没有意识到
[rdi]
+完整的imm32有2个条目,但显然SnB就是这样。
使用
jmp / jcc rel32
而不是
rel8
. 理想情况下,尝试在您要扩展的区域之外不需要更长跳转编码的地方扩展指令。
先跳后跳,先跳后跳,后跳后跳,
如果他们在其他地方接近需要rel32。i、 e.尽量避免在分支与其目标之间填充,除非您希望该分支使用rel32。
你可能会想编码
mov eax, [symbol]
作为6字节
a32 mov eax, [abs symbol]
在64位代码中,使用地址大小前缀来使用32位绝对地址。但是
this does cause a Length-Changing-Prefix stall
当它在Intel CPU上解码时。幸运的是,如果不显式指定32位地址大小,而使用7字节,那么默认情况下NASM/YASM/gas/clang都不会进行这种代码大小优化
mov r32, r/m32
使用ModR/M+SIB+disp32绝对寻址模式
mov eax, [abs symbol]
.
在64位位置相关代码中,绝对寻址是使用1个额外字节而不是RIP相对字节的廉价方法
. 但请注意,32位绝对+立即从uop缓存中提取需要2个周期,这与RIP relative+imm8/16/32只需要1个周期不同,尽管它仍然为指令使用2个条目。(例如
压敏电阻
-存储或a
cmp
). 所以
cmp [abs symbol], 123
从uop缓存中提取的速度比
cmp [rel symbol], 123
,即使两者各有2个条目。如果没有即时付款,则无需支付额外费用
请注意,即使对于可执行文件,PIE可执行文件也允许ASLR,
and are the default in many Linux distro
,因此,如果您可以保持代码PIC,而不存在任何性能方面的缺点,那么这是最好的。
当您不需要REX前缀时,请使用REX前缀,例如。
db 0x40
/
add eax, ecx
.
一般来说,添加当前CPU忽略的rep之类的前缀是不安全的,因为它们在未来的ISA扩展中可能意味着其他东西。
重复相同的前缀有时是可能的(但REX不可能)。例如
db 0x66, 0x66
/
add ax, bx
给出指令3操作数大小前缀,我认为它总是严格等价于前缀的一个副本。在某些CPU上,最多3个前缀是有效解码的限制。但这只有在你有一个前缀可以首先使用的情况下才有效;通常不使用16位操作数大小,通常也不需要32位地址大小(尽管在位置相关代码中访问静态数据是安全的)。
A.
ds
或
ss
访问内存的指令的前缀是no op
,并且可能不会导致任何当前CPU上的速度减慢。(@prl在评论中建议)。
事实上
Agner Fog的微通道指南使用
ds公司
a上的前缀
movq
[esi+ecx],mm0
在里面
示例7.1。排列IFETCH块
为PII/PIII(无循环缓冲区或uop缓存)优化循环,将其从每个时钟3次迭代加速到2次。
有些CPU(如AMD)在指令前缀超过3个时解码速度较慢。在某些CPU上,这包括SSE2中的强制前缀,尤其是SSSE3/SSE4.1指令中的前缀。在Silvermont中,即使是0F转义字节也会计数。
AVX指令可以使用2或3字节的VEX前缀
. 一些指令需要3字节的VEX前缀(第二个源是x/ymm8-15,或SSSE3或更高版本的强制前缀)。但是,可以使用2字节前缀的指令始终可以使用3字节VEX进行编码。NASM或气体
{vex3} vxorps xmm0,xmm0
. 如果AVX512可用,也可以使用4字节EVEX。
使用64位操作数大小
压敏电阻
即使你不需要它
例如
mov rax, strict dword 1
强制NASM中的7字节符号扩展imm32编码,
which would normally optimize it to 5-byte
mov eax, 1
.
mov eax, 1 ; 5 bytes to encode (B8 imm32)
mov rax, strict dword 1 ; 7 bytes: REX mov r/m64, sign-extended-imm32.
mov rax, strict qword 1 ; 10 bytes to encode (REX B8 imm64). movabs mnemonic for AT&T.
你甚至可以
mov reg, 0
而不是
xor reg,reg
.
mov r64, imm64
当常量实际上很小时,可以有效地适应uop缓存(适合32位符号扩展)
1个uop缓存条目,加载时间=1,与
mov r32, imm32
. 对一条巨型指令进行解码意味着,在一个16字节的解码块中,可能没有空间在同一周期内对其他3条指令进行解码,除非它们都是2字节的。可能稍微延长多条其他指令比使用一条长指令要好。
解码额外前缀的惩罚:
-
P5:前缀阻止配对,只有PMMX上的地址/操作数大小除外。
-
PPro至PIII:
如果一条指令有多个前缀,则总会有一个惩罚。这种惩罚通常是每个额外前缀一个时钟。
(Agner微阵列指南,第6.3节末尾)
-
Silvermont:如果你在乎的话,这可能是对前缀使用的最严格限制。解码暂停超过3个前缀,强制前缀+0个转义字节计数。SSSE3和SSE4指令已经有3个前缀,因此即使是REX也会使其解码速度变慢。
-
一些AMD:可能有3个前缀的限制,
不
包括转义字节,可能不包括SSE指令的强制前缀。
... 待办事项:完成此部分。在此之前,请参考Agner Fog的Microach指南。
在手工编码之后,一定要分解二进制文件以确保正确
. 不幸的是,NASM和其他汇编程序无法更好地支持在指令区域上选择廉价的填充来达到给定的对齐边界。
汇编程序语法
NASM有一些编码覆盖语法
:
{vex3}
和
{evex}
前缀,
NOSPLIT
和
strict byte / dword
,并在寻址模式内强制disp8/disp32。请注意
[rdi + byte 0]
是不允许的
byte
关键字必须放在第一位。
[byte rdi + 0]
是允许的,但我觉得这看起来很奇怪。
从列出
nasm -l/dev/stdout -felf64 padding.asm
line addr machine-code bytes source line
num
4 00000000 0F57C0 xorps xmm0,xmm0 ; SSE1 *ps instructions are 1-byte shorter
5 00000003 660FEFC0 pxor xmm0,xmm0
6
7 00000007 C5F058DA vaddps xmm3, xmm1,xmm2
8 0000000B C4E17058DA {vex3} vaddps xmm3, xmm1,xmm2
9 00000010 62F1740858DA {evex} vaddps xmm3, xmm1,xmm2
10
11
12 00000016 FFC0 inc eax
13 00000018 83C001 add eax, 1
14 0000001B 4883C001 add rax, 1
15 0000001F 678D4001 lea eax, [eax+1] ; runs on fewer ports and doesn't set flags
16 00000023 67488D4001 lea rax, [eax+1] ; address-size and REX.W
17 00000028 0501000000 add eax, strict dword 1 ; using the EAX-only encoding with no ModR/M
18 0000002D 81C001000000 db 0x81, 0xC0, 1,0,0,0 ; add eax,0x1 using the ModR/M imm32 encoding
19 00000033 81C101000000 add ecx, strict dword 1 ; non-eax must use the ModR/M encoding
20 00000039 4881C101000000 add rcx, strict qword 1 ; YASM requires strict dword for the immediate, because it's still 32b
21 00000040 67488D8001000000 lea rax, [dword eax+1]
22
23
24 00000048 8B07 mov eax, [rdi]
25 0000004A 8B4700 mov eax, [byte 0 + rdi]
26 0000004D 3E8B4700 mov eax, [ds: byte 0 + rdi]
26 ****************** warning: ds segment base generated, but will be ignored in 64-bit mode
27 00000051 8B8700000000 mov eax, [dword 0 + rdi]
28 00000057 8B043D00000000 mov eax, [NOSPLIT dword 0 + rdi*1] ; 1c extra latency on SnB-family for non-simple addressing mode
气体有
encoding-override pseudo-prefixes
{vex3}
,
{evex}
,
{disp8}
和
{disp32}
These replace the now-deprecated
.s
,
.d8
and
.d32
suffixes
.
天然气没有对即时大小的覆盖,只有位移。
GAS确实允许您添加显式
ds公司
前缀,带
ds mov src,dst
gcc -g -c padding.S && objdump -drwC padding.o -S
,手动编辑:
# no CPUs have separate ps vs. pd domains, so there's no penalty for mixing ps and pd loads/shuffles
0: 0f 28 07 movaps (%rdi),%xmm0
3: 66 0f 28 07 movapd (%rdi),%xmm0
7: 0f 58 c8 addps %xmm0,%xmm1 # not equivalent for SSE/AVX transitions, but sometimes safe to mix with AVX-128
a: c5 e8 58 d9 vaddps %xmm1,%xmm2, %xmm3 # default {vex2}
e: c4 e1 68 58 d9 {vex3} vaddps %xmm1,%xmm2, %xmm3
13: 62 f1 6c 08 58 d9 {evex} vaddps %xmm1,%xmm2, %xmm3
19: ff c0 inc %eax
1b: 83 c0 01 add $0x1,%eax
1e: 48 83 c0 01 add $0x1,%rax
22: 67 8d 40 01 lea 1(%eax), %eax # runs on fewer ports and doesn't set flags
26: 67 48 8d 40 01 lea 1(%eax), %rax # address-size and REX
# no equivalent for add eax, strict dword 1 # no-ModR/M
.byte 0x81, 0xC0; .long 1 # add eax,0x1 using the ModR/M imm32 encoding
2b: 81 c0 01 00 00 00 add $0x1,%eax # manually encoded
31: 81 c1 d2 04 00 00 add $0x4d2,%ecx # large immediate, can't get GAS to encode this way with $1 other than doing it manually
37: 67 8d 80 01 00 00 00 {disp32} lea 1(%eax), %eax
3e: 67 48 8d 80 01 00 00 00 {disp32} lea 1(%eax), %rax
mov 0(%rdi), %eax # the 0 optimizes away
46: 8b 07 mov (%rdi),%eax
{disp8} mov (%rdi), %eax # adds a disp8 even if you omit the 0
48: 8b 47 00 mov 0x0(%rdi),%eax
{disp8} ds mov (%rdi), %eax # with a DS prefix
4b: 3e 8b 47 00 mov %ds:0x0(%rdi),%eax
{disp32} mov (%rdi), %eax
4f: 8b 87 00 00 00 00 mov 0x0(%rdi),%eax
{disp32} mov 0(,%rdi,1), %eax # 1c extra latency on SnB-family for non-simple addressing mode
55: 8b 04 3d 00 00 00 00 mov 0x0(,%rdi,1),%eax
GAS在表示比所需编码更长的代码方面,严格来说不如NASM强大。