- 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/
我正在尝试在现有指令的基础上构建一个新指令,但我在我的过程中停止了。加载页面时,我面临以下错误: Multiple directives [directive#1, directive#2] aski
我是 angularjs 世界的新手,我只需要在数字类型的输入中输入从 1 到 10 的数字。不使用 HTML5 的 min 和 max 属性 我在 Jquery 中找到了一个示例,能否帮我将其转换为
我想使用 ionic与 Material 设计。我被困在使用带有自定义 CSS 的 ionic 指令和 angular-material 之间。 我读过使用 ionic 指令我们得到了很多高效的特性,
我创建了以下代码: var node = document.getElementById('TreeList'); var keys = Object.keys(model[0]); var trac
在 AngularJs 中没有提供 ng-enabled 指令。是否有任何适当的理由不在框架中提供该指令,因为当您可以使用 ng- 时,我们同时拥有 ng-show 和 ng-hide隐藏来实现我们的
我最近制作的程序有问题。基本上,它是 John Conway 人生游戏的简单版本,但它运行不正常。问题出在读取单元格及其邻居的状态并决定该单元格的 future 状态的代码中。这是代码的一部分(有点长
Dockerfile reference关于 FROM 指令的内容如下: FROM can appear multiple times within a single Dockerfile in or
我一直在尝试理解指令中孤立作用域和继承作用域之间的区别。这是我准备让自己理解的一个例子: HTML Inside isolated scope directive: {{m
知道如何从指令内部访问属性值吗? angular.module('portal.directives', []) .directive('languageFlag', ['$r
我正在通过将 c 程序与其等价的汇编程序进行比较来学习汇编。 这是代码。 .file "ex3.c" .section .rodata .LC0: .string "I am %d
我正在尝试写一个 Jenkinsfile并行执行一系列步骤。目标是拥有两个 agents (又名。 nodes )。一个应该进行 Windows 构建,另一个应该进行 linux 构建。但是,我不希望
我想知道为什么指令 FYL2XP1在 x86 架构上精确计算数学公式 y · log2(x + 1)。 这个公式有什么特别之处? 最佳答案 y操作数通常是编译时常量,暂时忘记 x + 1 . 自 lo
这个问题已经有答案了: Parameterize an SQL IN clause (41 个回答) 已关闭 8 年前。 第一个声明: Select GroupMember FROM Group 结果
我从 this question fork 并编辑了一个 plunker 我想做的是在数据加载后更新/填充 SELECT 元素(组合框),但有些事情不对劲。我检索数据,它位于 SELECT 元素的范围
我想创建一个简单的 markdown 指令,它接受元素中的一些内容,解析它并用 html 替换它。 所以这样: #Heading 或这个(其中 $scope.heading = '#Heading';
我对 Ansible 还很陌生,对于我对 local_action 指令的理解有一个简单的问题。 这是否意味着该命令完全在本地执行?假设你有这样的东西: local_action: command w
我有以下 HTML: ... ... 以及以下指令: myApp.directive('specialInput', ['$timeout', function($timeout)
如何在 .htaccess 中创建 Apache 指令强制文件 .mp4和 .pdf去下载?目前它们出现在浏览器窗口中。相反,我希望出现一个下载文件对话框。 最佳答案 将以下内容添加到 .htacce
我的问题是关于 C 中的 fork() 指令。我有以下程序: void main(){ int result, status; result = fork(); if(result=
我想要一个类似于 ng-model 的属性指令。我只想另外将一个输入字段值绑定(bind)到一个范围变量(只是在一个方向输入字段 ->范围变量)。所以我刚刚尝试了这个指令,但无论如何我都无法调用该指令
我是一名优秀的程序员,十分优秀!