将 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
这不太值得,除非你真的想避免常量缓存未命中的可能性。平均而言,负载通常会提前出来。
我知道我们可以像这样将字符移动到 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
中的全一开始。请参阅
这个模式不似乎很容易生成。它是一种字节模式(不是字、双字或四字),并且 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
这不太值得,除非你真的想避免常量缓存未命中的可能性。平均而言,负载通常会提前出来。