将 XMM 寄存器设置为重复字节模式(广播常量字节)
Posted
技术标签:
【中文标题】将 XMM 寄存器设置为重复字节模式(广播常量字节)【英文标题】:Set an XMM register to a repeating byte pattern (broadcast a constant byte) 【发布时间】:2020-03-29 20:52:21 【问题描述】:我知道我们可以做这样的事情来将一个字符移动到一个 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)
内在函数所做的。)
【问题讨论】:
当所需字节为常量时,您正在做的是最快的方法。 我只是在寻找一种更好的方法(如果有的话)一些即时或(来自注册)的方法! 你的字节是常量还是变量?如果是常数,那么你做的已经是最快的方式了。 它是一个常量字节 在这种情况下,您的代码已经很理想了。根据您使用的汇编程序,可能会有某种times
或dup
指令使其更易于键入。如果这让您烦恼,您也可以定义一个宏。
【参考方案1】:
仅使用 SSE2,从内存中加载完整模式通常是您的最佳选择。
在您的 NASM 源代码中,您可以使用 times 16 db 0x20
以便于维护。
使用 SSE3,您可以使用 movddup
进行 8 字节广播加载。使用 AVX,您可以使用 vbroadcastss
进行 4 字节广播加载。 这些广播负载在现代 CPU 上非常好,仅在负载端口上运行,不需要 shuffle uop。 即它们与movaps
一样便宜支持它们的 CPU,除了一个或两个更多的代码大小。 vbroadcastf128
与 YMM 寄存器相同。
大多数编译器似乎没有意识到这一点,并且会通过 _mm_set1
进行常量传播,即使这会导致 32 字节常量而不是 4 字节,即使只是 mov...
在循环之前加载它,而不是将其折叠到 ALU 指令的内存操作数中。 (当 AVX512 可用时,广播加载仍然可以做到这一点。)Clang 有时会利用广播加载来获取简单的常量。
AVX2 增加了vpbroadcastb/w/d/q
,但只有 dword 和 qword 是纯加载微指令。字节和字广播加载需要 ALU shuffle uop,因此对于恒定字节模式,您可能只想广播加载一个重复字节 4 次的 dword。 (除非它是来自大型查找表的元素,然后使用字节或字广播负载或pmovsx
符号扩展负载或其他方式压缩表。
AVX512 添加了vpbroadcastb/w/d/e
from an integer register,所以如果你有AVX512VL,你可以mov eax, 0x20202020
/ vpbroadcastd xmm0, eax
。
使用 SSE2 至少需要 2 条指令,包括 ALU shuffle,像这样,可能不值得。
movd xmm0, [const_4B]
pshufd xmm0, xmm0, 0
一些重复的常数可以在几个指令中动态生成,从pcmpeqd xmm0,xmm0
开始。请参阅 What are the best instruction sequences to generate vector constants on the fly? 和 Agner Fog 的指南。
这种模式确实不看起来很容易生成。它是一种字节模式(不是 word、dword 或 qword),并且 SSE 移位最多只能在 word 粒度下使用。但是,如果我们知道跨字节边界移动的位为 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
除非您真的想避免常量缓存未命中的可能性,否则这不太值得。平均而言,负载通常会提前。
【讨论】:
你知道这个问题对于 GP 64 位寄存器的任何答案吗? @Noah:对于一个常数,通常只是mov rdi, 0x0101010101010101
或其他。对于非常数,imul rcx, rdi
具有 0x01 重复常量,在将字节零扩展为 RCX 之后。因此,乘数 movzx ecx, byte source
和 imul r64,r64
的最坏情况成本为 mov reg,imm64
。
啊,比轮班方法聪明多了!
@Noah:是的,可以“滥用”快速硬件乘法器来做许多巧妙的事情,包括将足够小的元素加到高字节中。 (How to count the number of set bits in a 32-bit integer?)。乘法是移位和加法运算,加法或不受其他值的位控制。 @phuclv 也在 How to create a byte out of 8 bool values (and vice versa)? 中很好地解释了机制以上是关于将 XMM 寄存器设置为重复字节模式(广播常量字节)的主要内容,如果未能解决你的问题,请参考以下文章