- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
假设您想将一系列x86汇编指令与某些边界对齐。例如,您可能希望将循环对齐到16或32字节边界,或打包指令,以便将其有效地放置在uop缓存或任何其他内容中。
实现此目的的最简单方法是单字节NOP指令,其后紧跟multi-byte NOPs。尽管后者通常更有效,但是两种方法都不是免费的:NOP使用前端执行资源,并且也计入现代x86上的4-wide1重命名限制。
另一种选择是以某种方式延长一些说明以获得所需的对齐方式。如果做到这一点而又不引入新的拖延,那似乎比NOP方法更好。如何在最近的x86 CPU上有效地延长指令时间?
在理想情况下,加长技术将同时是:
最佳答案
考虑使用温和的代码高尔夫来收缩代码,而不是来扩展代码,尤其是在循环之前。例如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缓存丢失常量的情况,因此它具有弥补运行更多uops的优势。)
如果尚未使用它们来加载“压缩”常量,则pmovsxbd
,movddup
或vpbroadcastd
的长度比movaps
长。 dword / qword广播加载是免费的(没有ALU uop,只有负载)。
如果您完全担心代码对齐,那么您可能会担心它在L1I缓存中的位置或uop缓存边界的位置,因此仅计算总uops就不再足够了,并且在其中增加了一些uops在您关心的对象之前阻止可能根本不是问题。
但是在某些情况下,您可能确实想要针对要对齐的块之前的指令优化解码吞吐量/ uop-cache使用率/总uops。
填充说明,例如要求的问题:
Agner Fog在这方面有一整节内容:“"Optimizing subroutines in assembly language" guide”中的是“10.6延长指令以便对齐”。 (lea
,push r/m64
和SIB的想法都来自那里,我复制了一两个句子/短语,否则,这个答案是我自己的工作,或者是不同的想法,或者是在查阅Agner指南之前写的。)
但是,当前的CPU尚未对其进行更新:lea eax, [rbx + dword 0]
的缺点要比mov eax, ebx
多,因为您错过了zero-latency / no execution unit mov
。如果它不在关键路径上,请继续努力。简单的lea
具有相当好的吞吐量,而具有较大寻址模式(甚至可能有一些段前缀)的LEA可以比mov
+ nop
更好地解码/执行吞吐量。
使用一般形式,而不是push reg
或mov reg,imm
之类的简短说明(无ModR / M)。例如使用2字节push r/m64
作为push rbx
。或者使用更长的等效指令,例如add dst, 1
而不是inc dst
,in cases where there are no perf downsides to inc
,因此您已经在使用inc
。
使用SIB字节。您可以通过使用单个寄存器作为索引来使NASM做到这一点,例如mov eax, [nosplit rbx*1]
(see also),但是与仅使用SIB字节编码mov eax, [rbx]
相比,这会损害负载使用延迟。索引寻址模式在SnB系列like un-lamination and not using port7 for stores上还有其他缺点。
因此,最好只使用不带索引reg 的ModR / M + SIB来编码base=rbx + disp0/8/32=0
。 (“无索引”的SIB编码是否则将意味着idx = RSP的编码)。 [rsp + x]
寻址模式已经需要一个SIB(base = RSP是转义码,表示有一个SIB),并且始终在编译器生成的代码中出现。因此,有充分的理由期望现在和将来,这种方法在解码和执行(甚至对于RSP以外的基本寄存器)方面都将是完全有效的。 NASM语法无法表达这一点,因此您必须手动编码。 objdump -d
中的GNU gas Intel语法对Agner Fog的示例10.20表示8b 04 23 mov eax,DWORD PTR [rbx+riz*1]
。 (riz
是一种虚构的索引零符号,表示存在没有索引的SIB)。我尚未测试GAS是否接受该输入。
使用仅需要imm32
或disp32
的指令的imm8
和/或disp0/disp32
形式。 Agner Fog对Sandybridge的uop缓存(microarch guide table 9.1)的测试表明,立即数/位移的实际值很重要,而不是指令编码中使用的字节数。我没有有关Ryzen的uop缓存的任何信息。
因此,NASM imul eax, [dword 4 + rdi], strict dword 13
(10个字节:操作码+ modrm + disp32 + imm32)将使用32small,32small类别并在uop缓存中占用1个条目,这与立即数或disp32实际上有16个以上有效位不同。 (这将需要2个条目,并且从uop缓存中加载它会花费一个额外的周期。)
根据Agner的表格,SnB始终等于8/16 / 32small。寄存器的寻址方式是完全相同的,无论是否没有位移,或者它是否都是32small,因此mov dword [dword 0 + rdi], 123456
需要2个条目,就像mov dword [rdi], 123456789
一样。我还没有意识到[rdi]
+完整的imm32接受了2个条目,但是显然在SnB上就是这种情况。
使用jmp / jcc rel32
代替rel8
。理想情况下,尝试在不需要扩展编码的地方扩展指令。 在跳转目标之后填充以进行较早的向前跳转,在跳转目标之前填充以进行较后的向后跳转,(如果它们接近在其他地方需要rel32)。即尝试避免分支和目标之间的填充,除非您仍然希望该分支使用rel32。
您可能会想通过使用地址大小前缀使用32位绝对地址来将mov eax, [symbol]
编码为64位代码中的6字节a32 mov eax, [abs symbol]
。但是this does cause a Length-Changing-Prefix stall在Intel CPU上解码时。幸运的是,如果您未显式指定32位地址大小,而是使用7字节mov r32, r/m32
和ModR / M + SIB + disp32绝对值,则默认情况下,没有NASM / YASM / gas / clang会执行此代码大小优化mov eax, [abs symbol]
的寻址模式。
在64位位置相关的代码中,相对于RIP相对的,绝对寻址是一种便宜的使用1个额外字节的方式。但是请注意,与RIP相对+ imm8 / 16/32相比,即使它仍使用2条指令,它也需要2个周期才能从uop缓存中获取,而不是RIP相对+ imm8 / 16/32。 (例如mov
-store或cmp
)。因此,即使两者都各自占用2个条目,从utop缓存中获取cmp [abs symbol], 123
的速度也比cmp [rel symbol], 123
慢。没有立即的服务,就不会有额外的费用
请注意,即使可执行文件and are the default in many Linux distro,PIE可执行文件也允许使用ASLR,因此,如果可以保留代码PIC而没有任何不利影响,那么那是更好的选择。
不需要时使用REX前缀,例如db 0x40
/ add eax, ecx
。
通常不安全地添加当前CPU忽略的诸如rep之类的前缀,因为在将来的ISA扩展中它们可能意味着其他含义。
有时可能会重复相同的前缀(不过REX不能)。例如,db 0x66, 0x66
/ add ax, bx
给出指令3个操作数大小的前缀,我认为它始终严格等同于该前缀的一个副本。在某些CPU上,最多3个前缀是有效解码的限制。但这仅在您具有可以首先使用的前缀的情况下有效;您通常不使用16位操作数大小,并且通常不希望使用32位地址大小(尽管使用位置相关代码访问静态数据是安全的)。
存取内存的指令上的ds
或ss
前缀是no-op ,并且可能不会导致任何当前CPU的速度下降。 (@prl在评论中建议了这一点)。
实际上, Agner Fog的微体系结构指南在示例7.1中在ds
上使用了movq
前缀。安排IFETCH块可以调整PII / PIII的循环(无循环缓冲区或uop缓存),将其从每个时钟3次迭代加速到2次。
[esi+ecx],mm0
当指令的前缀超过3个时,某些CPU(如AMD)会缓慢解码。在某些CPU上,这包括SSE2中的强制前缀,尤其是SSSE3 / SSE4.1指令中的强制前缀。在Silvermont中,即使0F转义字节也很重要。
AVX指令可以使用2字节或3字节的VEX前缀。有些指令需要3字节的VEX前缀(第二个源是x / ymm8-15,或者SSSE3或更高版本的强制前缀)。但是,可以使用2字节前缀的指令始终可以使用3字节VEX进行编码。 NASM或GAS {vex3} vxorps xmm0,xmm0
。如果有AVX512,则也可以使用4字节EVEX。
即使不需要使用mov
,也请使用64位操作数大小的,例如mov rax, strict dword 1
在NASM which would normally optimize it to 5-byte mov eax, 1
中强制使用7字节的符号扩展imm32编码。
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缓存。 1个uop缓存条目,并且load-time = 1,与
mov r32, imm32
相同。解码一条巨型指令意味着在16字节解码块中可能没有空间在同一周期内解码其他3条指令,除非它们全为2字节。可能稍微延长多条其他指令可能比拥有一条长指令更好。
{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
{vex3}
,{evex}
,{disp8}
和{disp32}
These replace the now-deprecated .s
, .d8
and .d32
suffixes。
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
关于performance - 可以使用哪些方法在现代x86上有效地扩展指令长度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48046814/
问题故障解决记录 -- Java RMI Connection refused to host: x.x.x.x .... 在学习JavaRMI时,我遇到了以下情况 问题原因:可
我正在玩 Rank-N-type 并尝试输入 x x .但我发现这两个函数可以以相同的方式输入,这很不直观。 f :: (forall a b. a -> b) -> c f x = x x g ::
这个问题已经有答案了: How do you compare two version Strings in Java? (31 个回答) 已关闭 8 年前。 有谁知道如何在Java中比较两个版本字符串
这个问题已经有答案了: How do the post increment (i++) and pre increment (++i) operators work in Java? (14 个回答)
下面是带有 -n 和 -r 选项的 netstat 命令的输出,其中目标字段显示压缩地址 (127.1/16)。我想知道 netstat 命令是否有任何方法或选项可以显示整个目标 IP (127.1.
我知道要证明 : (¬ ∀ x, p x) → (∃ x, ¬ p x) 证明是: theorem : (¬ ∀ x, p x) → (∃ x, ¬ p x) := begin intro n
x * x 如何通过将其存储在“auto 变量”中来更改?我认为它应该仍然是相同的,并且我的测试表明类型、大小和值显然都是相同的。 但即使 x * x == (xx = x * x) 也是错误的。什么
假设,我们这样表达: someIQueryable.Where(x => x.SomeBoolProperty) someIQueryable.Where(x => !x.SomeBoolProper
我有一个字符串 1234X5678 我使用这个正则表达式来匹配模式 .X|..X|X. 我得到了 34X 问题是为什么我没有得到 4X 或 X5? 为什么正则表达式选择执行第二种模式? 最佳答案 这里
我的一个 friend 在面试时遇到了这个问题 找到使该函数返回真值的 x 值 function f(x) { return (x++ !== x) && (x++ === x); } 面试官
这个问题在这里已经有了答案: 10年前关闭。 Possible Duplicate: Isn't it easier to work with foo when it is represented b
我是 android 的新手,我一直在练习开发一个针对 2.2 版本的应用程序,我需要帮助了解如何将我的应用程序扩展到其他版本,即 1.x、2.3.x、3 .x 和 4.x.x,以及一些针对屏幕分辨率
为什么案例 1 给我们 :error: TypeError: x is undefined on line... //case 1 var x; x.push(x); console.log(x);
代码优先: # CASE 01 def test1(x): x += x print x l = [100] test1(l) print l CASE01 输出: [100, 100
我正在努力温习我的大计算。如果我有将所有项目移至 'i' 2 个空格右侧的函数,我有一个如下所示的公式: (n -1) + (n - 2) + (n - 3) ... (n - n) 第一次迭代我必须
给定 IP 字符串(如 x.x.x.x/x),我如何或将如何计算 IP 的范围最常见的情况可能是 198.162.1.1/24但可以是任何东西,因为法律允许的任何东西。 我要带198.162.1.1/
在我作为初学者努力编写干净的 Javascript 代码时,我最近阅读了 this article当我偶然发现这一段时,关于 JavaScript 中的命名空间: The code at the ve
我正在编写一个脚本,我希望避免污染 DOM 的其余部分,它将是一个用于收集一些基本访问者分析数据的第 3 方脚本。 我通常使用以下内容创建一个伪“命名空间”: var x = x || {}; 我正在
我尝试运行我的test_container_services.py套件,但遇到了以下问题: docker.errors.APIError:500服务器错误:内部服务器错误(“ b'{” message
是否存在这两个 if 语句会产生不同结果的情况? if(x as X != null) { // Do something } if(x is X) { // Do something } 编
我是一名优秀的程序员,十分优秀!