【发布时间】:2011-10-03 00:05:13
【问题描述】:
上面已经有a question,但它被关闭为“模棱两可”,所以我要打开一个新的 - 我找到了答案,也许它也会对其他人有所帮助。
问题是:如何编写一系列汇编代码来初始化一个具有 128 位立即数(常数)值的 XMM 寄存器?
【问题讨论】:
上面已经有a question,但它被关闭为“模棱两可”,所以我要打开一个新的 - 我找到了答案,也许它也会对其他人有所帮助。
问题是:如何编写一系列汇编代码来初始化一个具有 128 位立即数(常数)值的 XMM 寄存器?
【问题讨论】:
只是想补充一点,可以在 Agner Fog 的手册 Optimizing subroutines in assembly language,生成常量,第 13.8 节,第 124 页中阅读有关使用汇编生成各种常量的信息。
【讨论】:
您可以这样做,只需一条movaps 指令:
.section .rodata # put your constants in the read-only data section
.p2align 4 # align to 16 = 1<<4
LC0:
.long 1082130432
.long 1077936128
.long 1073741824
.long 1065353216
.text
foo:
movaps LC0(%rip), %xmm0
通过数据加载来加载它通常比将其嵌入到指令流中更可取,尤其是因为它需要多少指令。这是 CPU 需要执行的几个额外的微指令,对于一个无法通过几次移位的全一生成的任意常数。
如果更简单,您可以将常量放在 jit 编译的函数之前或之后,而不是放在单独的部分中。但由于 CPU 已分离 L1d / L1i 高速缓存和 TLB,因此通常最好将常量组合在一起,与指令分开。
如果常量的两半相同,则可以使用 SSE3 广播加载它movddup (m64), %xmm0。
【讨论】:
.align 指令采用 2 的幂参数,所以 .align 4 表示对齐 2 的倍数^ 4 = 16 个字节。
.p2align 4 将是一个不错的选择。它总是意味着 2 的幂对齐,并被引入以阻止 .align 的疯狂,这意味着不同汇编器(或同一汇编器的版本?)上的不同事物。我认为它比 SSE 存在的时间更长,因此推荐它应该是安全的。
作为 10000 种方法之一,使用 SSE4.1 pinsrq
mov rax, first half
movq xmm0, rax ; better than pinsrq xmm0,rax,0 for performance and code-size
mov rax, second half
pinsrq xmm0, rax, 1
【讨论】:
pinsertq 记录在哪里?我在任何英特尔说明手册中都找不到该说明。
movq 指令不允许通用寄存器作为第二个操作数。所以这“更快”只是因为它不能很快地组装起来。从好的方面来说,pinsrq 技巧很有效。
movq 有两种形式:您可能会想到MOVQ xmm1, xmm2/m64,它可以在 32 位或 64 位模式下组装。但这当然是使用MOVQ xmm, r/m64 形式,即REX+MOVD,仅在64位模式下可用。显然有些汇编器仍然称它为movd,所以如果它不能汇编,试试movd xmm0, rax。或者更好的是,使用movdqa 加载一个常量。
最好的解决方案(特别是如果您想坚持使用 SSE2 - 即避免使用 AVX)用立即值的两个 64 位一半初始化两个寄存器(例如 xmm0 和 xmm1),请执行 MOVLHPS xmm0,xmm1 为了初始化一个 64 位的值,最简单的解决方案是使用通用寄存器(比如 AX),然后使用 MOVQ 将其值传输到 XMM 寄存器。 所以顺序应该是这样的:
MOV RAX, <first_half>
MOVQ XMM0, RAX
MOV RAX, <second_half>
MOVQ XMM1, RAX
MOVLHPS XMM0,XMM1
【讨论】:
PINSRQ xmm0, rax, 1 可以替换movq / movlhps。此外,您应该说 RAX,而不仅仅是 AX。 AX 特指RAX 的低16 位。您可以称它为 A,但这只是令人困惑。无论如何,这比仅使用加载指令加载它更糟糕。
punpcklqdq xmm0, xmm1 可能是比movlhps 更好的选择。对于常量,显然无序执行可以隐藏从 FP shuffle 到整数指令的旁路延迟(在重要的 CPU 上),但这并没有什么坏处。无论如何,我认为在大多数代码中,最好从.rodata 部分加载一个常量,而不是将其嵌入到指令流中。通常 uop-cache 空间很有价值,前端吞吐量也是如此。单个movdqa 要快得多,除非它在缓存中丢失。但如果经常运行它就不会
在指令流中嵌入常量有多种方式:
因此,虽然没有办法立即加载到XMM 寄存器,但可以从“紧挨着”存储的值到代码执行的地方。这会产生类似的东西:
.align 4
.val:
.long 0x12345678
.long 0x9abcdef0
.long 0xfedbca98
.long 0x76543210
func:
movdqa .val(%rip), %xmm0
拆机时:
0000000000000000 : 0: 78 56 34 12 f0 de bc 9a 8: 98 ca db fe 10 32 54 76 0000000000000010 : 10: 66 0f 6f 05 e8 ff ff movdqa -0x18(%rip),%xmm0 # 0
非常紧凑,23 字节。
其他选项是在堆栈上构造值并再次从那里加载它。在 32 位 x86 中,您没有 %rip-relative 内存访问,仍然可以在 24 字节内完成此操作(假设堆栈指针在入口处对齐;否则,需要未对齐的加载):
00000000 : 0: 68 78 56 34 12 推 $0x12345678 5: 68 f0 de bc 9a push $0x9abcdef0 答:68 98 ca db fe push $0xfedbca98 f: 68 10 32 54 76 推 $0x76543210 14: 66 0f 6f 04 24 movdqa (%esp),%xmm0
在 64 位中(ABI 保证函数入口处的堆栈指针对齐)需要 27 个字节:
0000000000000000 : 0: 48 b8 f0 de bc 9a 78 56 34 12 movabs $0x123456789abcdef0,%rax a: 50 推 %rax b: 48 b8 10 32 54 76 98 ba dc fe movabs $0xfedcba9876543210,%rax 15:50 推%rax 16: 66 0f 6f 04 24 movdqa (%rsp),%xmm0
如果您将其中任何一个与MOVLHPS 版本进行比较,您会发现它是最长的:
0000000000000000 : 0: 48 b8 f0 de bc 9a 78 56 34 12 movabs $0x123456789abcdef0,%rax a: 66 48 0f 6e c0 movq %rax,%xmm0 f: 48 b8 10 32 54 76 98 ba dc fe movabs $0xfedcba9876543210,%rax 19: 66 48 0f 6e c8 movq %rax,%xmm1 1e: 0f 16 c1 movlhps %xmm1,%xmm0
33 字节。
直接从指令内存加载的另一个优点是movdqa 不依赖于之前的任何内容。最有可能的是,@Paul R 给出的第一个版本是您可以获得的最快版本。
【讨论】:
pcmpeqw xmm0,xmm0 / psrld xmm0, 31 生成四个 32 位整数 1 值的向量),或者可能将立即数移动到寄存器 movq,并使用pshufd.