使用 x64 SSE / AVX 寄存器反转字符串

String reverse with x64 SSE / AVX registers

我正在尝试编写 SIMD 汇编指令来反转长度在 16 到 32 字节之间的字符串。下面的代码反转了一个恰好 32 个字节长的字符串,但不处理任何更短的字符串。是否有一种 AVX/SSE 方法可以以更简洁的方式更好地做到这一点?我实际上需要一个用于 xmm 或 ymm 的 bswap。

Rdx 指向内存中的某处,其中包含我想要反转的空终止字符串。反转后,我想在同一地址用它的反转版本覆盖字符串。

movdqu xmm0, [rdx]
pshufd xmm0,xmm0, 0x1B    
pshuflw xmm0,xmm0, 0xB1
pshufhw xmm0, xmm0, 0xB1
movdqa xmm1,xmm0
psrlw xmm1, 8
psllw xmm0, 8    
por xmm0,xmm1 

movdqu xmm2, [rdx +0x10]
pshufd xmm2,xmm2, 0x1b    
pshuflw xmm2,xmm2, 0xB1
pshufhw xmm2, xmm2, 0xB1
movdqa xmm3,xmm2
psrlw xmm3, 8
psllw xmm2, 8    
por xmm2,xmm3

movdqu [rdx], xmm2
movdqu [rdx+0x10], xmm0

pshufb 加载一个控制向量,通过一次洗牌来反转整个向量。 在 Intel 上您只能获得 1 次洗牌/时钟吞吐量,但是 vpshufb ymm 仍然是单个 uop。 (https://agner.org/optimize/)

所以加载32个字节,用vpshufb对128位通道进行字节反转,然后用vextracti128将两半分开存储。或者做窄负载和宽存储,这可能更好地避免存储转发停顿。

或者使用额外的随机播放在 32 字节加载/32 字节存储之间交换 YMM 的一半。 (例如 vpermqvperm2i128 换道,在 vpshufb 之前或之后)。

default rel

byte_rev_32:
    ...
    vmovdqu      xmm0, [rdx + 16]         ; 1 uop
    vinserti128  ymm0, ymm0, [rdx], 1     ; 2 uops: load + any vector-ALU port
    ; lane-swapping load that doesn't cost any port-5-only shuffle uops

    ; then in-lane byte reverse
    vpshufb      ymm0, ymm0, [byte_reverse]   ; 1 uop (with micro-fused load)

    vmovdqu      [rdx], ymm0
    ...

section .rodata:
 align 32
 byte_reverse: db 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
               db 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0

或者,如果您在循环中执行此操作,则应提升 shuffle-control 向量的负载。例如VBROADCASTI128 ymm1, [byte_reverse] 所以你只需要在内存中有一个 16 字节的常量。在 Intel CPU 上,具有双字和更大粒度的广播负载与常规负载一样便宜。

AVX512VBMI(CannonLake / Ice Lake)具有交叉通道vpermb,可以在 1 条指令中对 32 或 64 字节向量进行字节反转。

或者仅针对 pshufb 使用 SSSE3,而不使用 AVX2,只需加载两个 16 字节的一半,分别交换它们,并分别存储它们。