将 XMM 寄存器设置为重复字节模式(广播常量字节)

Set an XMM register to a repeating byte pattern (broadcast a constant byte)

我知道我们可以像这样将字符移动到 xmm 寄存器:

movaps xmm1, xword [.__0x20]

align 16
.__0x20 db 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20

但由于这是一个记忆过程,我想知道有没有更好的方法? (另外,我说的是 SSE2 而不是其他 SIMD 类型……)

我希望 xmm1 寄存器的每个字节都是 0x20 而不是只有一个字节..

(编者按:这可以称为广播或splat。
这就是 _mm_set1_epi8(0x20) 内在函数的作用。)

只有 SSE2,从内存中加载完整模式通常是最好的选择。

在您的 NASM 源代码中,您可以使用 times 16 db 0x20 以便于维护。


使用 SSE3,您可以使用 movddup. With AVX you can do a 4-byte broadcast-load with vbroadcastss 进行 8 字节的广播加载。 这些广播加载在现代 CPU 上非常好,运行 在 上只是 加载端口,不需要 shuffle uop。 即它们在支持它们的 CPU 上,它们与 movaps 一样便宜,除了多一两个字节的代码大小。 vbroadcastf128 到 YMM 寄存器也是如此。

大多数编译器似乎没有意识到这一点,并且会通过 _mm_set1 进行常量传播,即使结果是 32 字节常量而不是 4 字节,即使只是 mov... 加载它在循环之前,而不是将其折叠到 ALU 指令的内存操作数中。 (当 AVX512 可用时,广播加载仍然是可能的。)Clang 有时会利用广播加载来获取简单的常量。

AVX2 增加了vpbroadcastb/w/d/q,但只有dword 和qword 是纯加载微指令。字节和字广播加载需要一个 ALU shuffle uop,因此对于常量字节模式,您可能只想广播加载一个重复一个字节 4 次的双字。 (除非它是来自大查找 table 的元素,然后使用字节或字广播负载或 pmovsx 符号扩展负载或其他方式压缩 table)。

AVX512 添加了 vpbroadcastb/w/d/e from an integer register,因此如果您有 AVX512VL,您可以 mov eax, 0x20202020 / vpbroadcastd xmm0, eax


使用 SSE2 至少需要 2 条指令,包括 ALU 洗牌,像这样,可能不值得。

    movd    xmm0, [const_4B]
    pshufd  xmm0, xmm0, 0

一些重复常量可以在几条指令中即时生成,从 pcmpeqd xmm0,xmm0 中的全一开始。请参阅 和 Agner Fog 的指南。

这个模式似乎很容易生成。它是一种字节模式(不是字、双字或四字),并且 SSE 移位最多只能以字粒度使用。但是,如果我们知道跨字节边界移动的位为 0,就可以了。例如

   pcmpeqd  xmm0, xmm0     ; set1( -1 )
   pabsb    xmm0, xmm0     ; set1_epi8(1)    SSSE3
   pslld    xmm0, 5        ; set1_epi8(1<<5)

; or with only SSE2, something even less efficient like shift / packsswb / shift

这不太值得,除非你真的想避免常量缓存未命中的可能性。平均而言,负载通常会提前出来。