我的尝试......来自演示派对上的音乐组合(或者更有可能来自旅行)让我头疼,所以我放弃了imul rax,rax,imm32
用于复制31位并尝试将1位保存在符号中然后修补中间结果,因为它有几个问题,我没有预见到.
所以我反而选择了鸡蛋,并且只复制2x 16位的单词,然后以一种Jester的方式进行重新洗牌(xchg al,ah
我发誓当他发布答案的时候,我发誓).
; rax = 00001234 (bytes, not hexa digits)
ror rax, 16 ; 34000012
imul rax, rax, 0x00010001 ; 34001212
shr rax, 16 ; 00340012
imul rax, rax, 0x00010001 ; 34341212
ror rax, 24 ; 21234341
xchg al, ah ; 21234314
ror rax, 8 ; 42123431
xchg al, ah ; 42123413
rol rax, 16 ; 12341342
xchg al, ah ; 12341324
ror rax, 8 ; 41234132
xchg al, ah ; 41234123
rol rax, 8 ; 12341234
Run Code Online (Sandbox Code Playgroud)
一个较短的(指令计数)变体(rol ...,8
从第5个开始只有使用指令的有趣转折):
; eax = 00001234 (bytes, not hexa digits)
ror rax, 8 ; 40000123
imul rax, rax, 0x01000001 ; 40123123
rol rax, 16 ; 12312340
mov al, ah ; 12312344
rol rax, 8 ; 23123441
rol ax, 8 ; 23123414
rol rax, 8 ; 31234142
rol ax, 8 ; 31234124
rol rax, 8 ; 12341243
rol ax, 8 ; 12341234
Run Code Online (Sandbox Code Playgroud)
必须有一个更简单的方法:-D
shl rax, 8 ; 00012340
mov al, ah ; 00012344
bswap rax ; 44321000
shr rax, 16 ; 00443210
mov al, ah ; 00443211
ror rax, 8 ; 10044321
xchg ah, al ; 10044312
rol rax, 8 ; 00443121
xchg ah, al ; 00443112
shl rax, 8 ; 04431120
mov al, ah ; 04431122
ror rax, 32 ; 11220443
xchg ah, al ; 11220434
ror rax, 8 ; 41122043
xchg ah, al ; 41122034
ror rax, 8 ; 44112203
mov ah, al ; 44112233
ror rax, 8 ; 34411223
xchg ah, al ; 34411232
rol rax, 16 ; 41123234
xchg ah, al ; 41123243
ror rax, 8 ; 34112324
xchg ah, al ; 34112342
rol rax, 24 ; 12342341
xchg ah, al ; 12342314
ror rax, 8 ; 41234231
xchg ah, al ; 41234213
ror rax, 8 ; 34123421
xchg ah, al ; 34123412
ror rax, 16 ; 12341234
Run Code Online (Sandbox Code Playgroud)
如果不按要求接触第二个寄存器,就很难有效地做到这一点。评论中唯一不会影响性能的建议是使用 64 位常量imul
:
; mov eax,eax ; if eax isn't already zero-extended into rax
imul rax, [rel broadcast_low32_multiplier]
section .rodata
broadcast_low32_multiplier: dq 0x100000001
Run Code Online (Sandbox Code Playgroud)
这在 Intel Sandybridge 系列 CPU 和 AMD Ryzen 上具有每时钟 1 个吞吐量(和 3 个周期延迟)。但是推土机系列每 4 个时钟只有一个。( http://agner.org/optimize/ , https://uops.info/ )
你不能用 来做这件事imul r64, r64, imm32
,因为我们的常量有 33 位。 您可以将 0x100000001 分解为0x663d81 * 0x281
,并在具有快速乘法器的 CPU 上以 2 uop 和 6 周期延迟进行。(谢谢,@stepan)
;mov ecx, eax ; zero extend into a different reg with 0 latency, and imul from there, if not already zero-extended
imul rax, rax, 0x281
imul rax, rax, 0x663d81 ; 0x281 * 0x663d81 = 0x100000001
Run Code Online (Sandbox Code Playgroud)
制作一个完全独立的 x86-64 机器代码片段的另一种选择是将常量内联按照说明跳过它。
; mov eax,eax ; if eax isn't already zero-extended into rax
imul rax, [rel broadcast_low32_multiplier]
jmp after_constant
broadcast_low32_multiplier: dq 0x100000001
after_constant:
Run Code Online (Sandbox Code Playgroud)
从正确性 POV 来看,这与使用立即数的指令没有区别。NASM 列表:
1 00000000 480FAF0502000000 imul rax, [rel broadcast_low32_multiplier]
2 00000008 EB08 jmp after_constant
3 0000000A 0100000001000000 broadcast_low32_multiplier: dq 0x100000001
4 after_constant:
Run Code Online (Sandbox Code Playgroud)
它确实从与代码相同的页面加载数据,但 AFAIK 不可能使页面可执行但无法使用页表设置读取,因此在纯指令可以工作的情况下,这不会失败。不过,它将用完 L1D 缓存中的一条缓存线、一个 dTLB 条目以及 L1i 缓存和一个 iTLB 条目。它甚至可能会错过在DTLB条目是否只在ITLB热,可能会错过L1D缓存(但很可能砸在L2高速缓存;最CPU有一些非排他性的统一高速缓存是取指经过看到的。这更多混合代码和数据的性能缺乏好处。)
Jester 在 2 个 store / 1 reload 的评论中的建议是紧凑的,但会导致所有 CPU 上的store-forwarding 停顿,除了 in-order Atom(pre Silvermont):
push rax
mov [rsp+4], eax ; overwrite high bytes
pop rax ; store-forwarding stall when a wide load covers 2 narrow stores
Run Code Online (Sandbox Code Playgroud)
这可能是最小的总大小版本,特别是如果您有一个堆栈帧,可以让您使用类似的rbp+disp8
寻址模式[rbp-12]
,与rsp
-relative相比节省 1 个字节(即使没有索引寄存器也需要一个 SIB)。
(“另一种”标准方式是mov rcx, 0x100000001
/ imul rax, rcx
,但 64 位立即数很大,而 shift/OR 实际上是较低的延迟。)
如果性能很重要,那么避免完全按照您的要求去做可能是值得的。
例如,无论您在做什么,都可以保存/恢复其他 15 个通用寄存器之一,因此您可以将其用作临时寄存器。理想情况下围绕整个函数,因此您可以使用临时寄存器在循环内进行此广播。或者只是在不先保存的情况下进行破坏,如果您可以快速重新加载或重新计算。
; rax = garbage:eax
push rbx ; save/restore if needed, preferably at the top of the function
mov ebx, eax ; clear high garbage with 32-bit zero extend
shl rax, 32 ; clear low garbage via shifting
or rax, rbx ; or lea rcx, [rax+rbx] into a 3rd reg if you want
...
pop rbx ; preferably at the bottom of the function
Run Code Online (Sandbox Code Playgroud)
即使没有移动消除,这也有 2 个周期的延迟(对于 rax)。我们将原始 RAX 与复制(零扩展)并行移动到 RBX。如果 EAX 已经零扩展,则可以在 RBX 中移动副本,如果您正在优化具有 mov-elimination 的 CPU:当您覆盖 mov 目标时,英特尔的实现会释放 mov-elimination 跟踪资源。但这会使 CPU 的延迟更高,而无需消除移动,从而将 mov 置于关键路径上。(冰湖的微代码更新禁用了移动消除。/叹气。)
push/pop 将存储/重新加载引入到 RBX 的关键路径中,因此如果您实际上必须在每次广播时执行此操作,请明智地选择您的临时寄存器,而不是仅仅为整个循环或函数释放额外的寄存器。
或者使用 BMI2,rorx rcx, rax, 32
/or rax, rcx
只需 2 uop即可完成工作。(与 mov/shift 不同,为此必须已经将 RAX 的高 32 位清零)。
或者使用 SSE2(x86-64 的基线)。如果您可以首先使用 xmm0 而不是 rax :
; movd xmm0, eax ; instead of whatever was setting rax
punpckldq xmm0, xmm0 ; [dcba] -> [bbaa]
; movq rax, xmm0
Run Code Online (Sandbox Code Playgroud)
大多数 CPU 上的 1 个周期延迟(SlowShuffle Core2 / K8 除外,您可以使用pshuflw
正确的 imm8 来提高效率),加上使用 xmm0 而不是 rax 的任何额外成本。
如果您这样做,将 RAX 复制到 XMM0 或从 XMM0 复制是这里的大部分成本,例如 4 到 6 次循环往返,取决于微体系结构,在 shuffle 之上。(例如 uops.info 的Zen2 延迟测试包括以 6 个周期测量的movd/movq 往返)。这会破坏 xmm0,这可能好也可能不好。
但是,如果您一直使用 xmm0 作为整数值,则可以避免该成本。您可以在 xmm 寄存器中进行标量整数数学运算(忽略高位字节会发生什么)。可以使用paddd
/ paddq
、pslldq
、pmuludq
/pand
等指令。你不能做的主要事情是在寻址模式下使用它,但如果你广播低 32 位,这可能不是地址或索引。
归档时间: |
|
查看次数: |
537 次 |
最近记录: |