使用 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 的一半。 (例如 vpermq
或 vperm2i128
换道,在 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 字节的一半,分别交换它们,并分别存储它们。
我正在尝试编写 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 的一半。 (例如 vpermq
或 vperm2i128
换道,在 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 字节的一半,分别交换它们,并分别存储它们。