如何将字节复制到 xmm0 寄存器

how to copy bytes into xmm0 register

我有以下代码,它工作正常但似乎效率低下,因为最终结果只需要 xmm0

中的数据
         mov rcx, 16                       ; get first word, up to 16 bytes
         mov rdi, CMD                      ; ...and put it in CMD
         mov rsi, CMD_BLOCK
 @@:     lodsb
         cmp al, 0x20
         je @f
         stosb
         loop @b

 @@:     mov rsi, CMD                      ;
         movdqa xmm0, [rsi]                ; mov cmd into xmm0

我确定使用 SSE2、SSE4 等,有一个更好的方法不需要使用 CMD 缓冲区,但我正在努力弄清楚如何去做。

您的代码看起来是从 CMD_BLOCK 到第一个 0x20 之间获取字节,我假设它需要高于该值的零。

这甚至不是编写一次一个字节的复制循环的最有效方法。切勿使用 LOOP 指令,除非您专门针对 the few architectures where it's not slow (e.g. AMD Bulldozer). See Agner Fog's stuff, and other links from the 标签 wiki 之一进行调整。或者通过 C 内在函数使用 SSE/AVX,让编译器生成实际的 asm.

但更重要的是,如果您使用 SSE 指令,您甚至不需要循环。

我假设您在开始复制之前将 16B CMD 缓冲区归零,否则您还不如做一个未对齐的加载并获取超出您想要的数据的任何垃圾字节。

如果您可以安全地读过 CMD_BLOCK 的末尾而不会导致段错误,事情就会容易得多。希望你能安排它是安全的。例如确保它不在页面的末尾,后面是未映射的页面。如果没有,您可能需要执行对齐加载,然后如果您没有得到数据的结尾,则有条件地进行另一个对齐加载。


SSE2 pcmpeqb,找到第一个匹配项,并在该位置和更高位置找到零字节

section .rodata

ALIGN 32              ; No cache-line splits when taking an unaligned 16B window on these 32 bytes
dd -1, -1, -1, -1
zeroing_mask:
dd  0,  0,  0,  0

ALIGN 16
end_pattern:  times 16   db 0x20    ; pre-broadcast the byte to compare against  (or generate it on the fly)

section .text

    ... as part of some function ...
    movdqu   xmm0, [CMD_BLOCK]       ; you don't have to waste instructions putting pointers in registers.
    movdqa   xmm1, [end_pattern]     ; or hoist this load out of a loop
    pcmpeqb  xmm1, xmm0

    pmovmskb eax, xmm1
    bsr      eax, eax                ; number of bytes of the vector to keep
    jz    @no_match                  ; bsr is weird when input is 0 :(
    neg      rax                     ; go back this far into the all-ones bytes
    movdqu   xmm1, [zeroing_mask + rax]   ; take a window of 16 bytes
    pand     xmm0, xmm1
@no_match:                          ; all bytes are valid, no masking needed
    ;; XMM0 holds bytes from [CMD_BLOCK], up to but not including the first 0x20.

在 Intel Haswell 上,从 PCMPEQB 的输入到 PAND 的输出准备就绪,这应该有大约 11c 的延迟。

如果你能用LZCNT代替BSR,你就可以避开分支。你。因为在不匹配的情况下我们想要一个 16(所以 neg eax 给出 -16,并且我们加载一个全 1 的向量),一个 16 位的 LZCNT 就可以了。 (lzcnt ax, ax 有效,因为 RAX 的高位字节从 pmovmskb 开始已经为零。否则 xor ecx, ecx / lzcnt cx, ax

这种具有未对齐负载的掩码生成想法采用一些全 1 和全 0 的 window 与我在 上的一个答案相同。

有从内存中加载掩码的替代方法。例如将第一个全一字节广播到向量的所有较高字节,每次将屏蔽区域的长度加倍,直到它大到足以覆盖整个向量,即使 0xFF 字节是第一个字节。

    movdqu   xmm0, [CMD_BLOCK]
    movdqa   xmm1, [end_pattern]
    pcmpeqb  xmm1, xmm0             ; 0 0 ... -1 ?? ?? ...

    movdqa   xmm2, xmm1
    pslldq   xmm2, 1
    por      xmm1, xmm2             ; 0 0 ... -1 -1 ?? ...

    movdqa   xmm2, xmm1
    pslldq   xmm2, 2
    por      xmm1, xmm2             ; 0 0 ... -1 -1 -1 -1 ?? ...

    pshufd   xmm2, xmm1, 0b10010000  ; [ a b c d ] -> [ a a b c ]
    por      xmm1, xmm2              ; 0 0 ... -1 -1 -1 -1 -1 -1 -1 -1 ?? ... (8-wide)

    pshufd   xmm2, xmm1, 0b01000000  ; [ abcd ] -> [ aaab ]
    por      xmm1, xmm2              ; 0 0 ... -1 (all the way to the end, no ?? elements left)
    ;; xmm1 = the same mask the other version loads with movdqu based on the index of the first match

    pandn    xmm1, xmm0              ; xmm1 = [CMD_BLOCK] with upper bytes zeroed


    ;; pshufd instead of copy + vector shift  works:
    ;; [ abcd  efgh  hijk  lmno ]
    ;; [ abcd  abcd  efgh  hijk ]  ; we're ORing together so it's ok that the first 4B are still there instead of zeroed.
    

SSE4.2 PCMPISTRM:

如果您与终止符进行异或运算,使 0x20 字节变为 0x00 字节,则您可以使用 SSE4.2 字符串指令,因为它们已经设置为处理隐式长度字符串,其中所有字节超出0x00 无效。请参阅 this tutorial/example,因为英特尔的文档只是详细记录了 一切 ,而没有首先关注重要的内容。

PCMPISTRM 在 Skylake 上以 9 周期延迟运行,在 Haswell 上以 10c 延迟运行,在 Nehalem 上以 7c 延迟运行。所以这是关于 Haswell 延迟的收支平衡,或者实际上是损失,因为我们还需要 PXOR。寻找 0x00 字节并标记超出该字节的元素是硬编码的,因此我们需要异或将 0x20 字节变成 0x00。但它的微指令要少得多,代码量也更小。

;; PCMPISTRM imm8:
;; imm8[1:0] = 00 = unsigned bytes
;; imm8[3:2] = 10 = equals each, vertical comparison.  (always not-equal since we're comparing the orig vector with one where we XORed the match byte)
;; imm8[5:4] = 11 = masked(-): inverted for valid bytes, but not for invalid  (TODO: get the logic on this and PAND vs. PANDN correct)
;; imm8[6] = 1 = output selection (byte mask, not bit mask)
;; imm8[7] = 0 (reserved.  Holy crap, this instruction has room to encode even more functionality??)

movdqu     xmm1, [CMD_BLOCK]

movdqa     xmm2, xmm1
pxor       xmm2, [end_pattern]       ; turn the stop-character into 0x00 so it looks like an implicit-length string
                                     ; also creating a vector where every byte is different from xmm1, so we get guaranteed results for the "valid" part of the vectors (unless the input string can contain 0x0 bytes)
pcmpistrm  xmm1, xmm2, 0b01111000    ; implicit destination operand: XMM0
pand       xmm0, xmm1

我可能没有正确的 pcmpistrm 参数,但我没有时间测试它或在心理上验证它。可以这么说,我很确定有可能让它在第一个零字节之前和之后都是全一的掩码。