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

ELH*_*ERS 2 assembly sse micro-optimization sse2

我知道我们可以做这样的事情来将一个字符移动到一个 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Pet*_*des 5

仅使用 SSE2,从内存中加载完整模式通常是您最好的选择。

在您的 NASM 源代码中,您可以使用它times 16 db 0x20来轻松维护。


使用 SSE3,您可以使用movddup. 使用 AVX,您可以使用vbroadcastss. 这些广播负载在现代 CPU 上非常好,在负载端口上运行,不需要 shuffle uop。 即它们movaps与支持它们的 CPU完全一样便宜,除了一两个字节的代码大小。同为vbroadcastf128YMM 寄存器。

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

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

AVX512vpbroadcastb/w/d/e从整数寄存器添加因此您可以mov eax, 0x20202020/vpbroadcastd xmm0, eax如果您有 AVX512VL。


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

    movd    xmm0, [const_4B]
    pshufd  xmm0, xmm0, 0
Run Code Online (Sandbox Code Playgroud)

一些重复的常量可以在几个指令中即时生成,从pcmpeqd xmm0,xmm0. 请参阅动态生成向量常量的最佳指令序列是什么?和 Agner Fog 的指南。

这种模式似乎并不容易生成。它是一个字节模式(不是 word、dword 或 qword)并且 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
Run Code Online (Sandbox Code Playgroud)

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