将 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) 内在函数所做的。)

【问题讨论】:

当所需字节为常量时,您正在做的是最快的方法。 我只是在寻找一种更好的方法(如果有的话)一些即时或(来自注册)的方法! 你的字节是常量还是变量?如果是常数,那么你做的已经是最快的方式了。 它是一个常量字节 在这种情况下,您的代码已经很理想了。根据您使用的汇编程序,可能会有某种timesdup 指令使其更易于键入。如果这让您烦恼,您也可以定义一个宏。 【参考方案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 sourceimul 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 寄存器设置为重复字节模式(广播常量字节)的主要内容,如果未能解决你的问题,请参考以下文章

SSE XMM 点积说明

如何将两个打包的 64 位四字加载到 128 位 xmm 寄存器中

如何将浮点常量值移动到 xmm 寄存器中?

如何将浮点常量值移动到xmm寄存器中?

解压缩位域(movmskb 的逆)

如何“删除” SSE 寄存器末尾的字节?