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

Posted

技术标签:

【中文标题】使用 x64 SSE / AVX 寄存器进行字符串反转【英文标题】:String reverse with x64 SSE / AVX registers 【发布时间】:2019-08-08 00:52:55 【问题描述】:

我正在尝试编写 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。所以加载 32 个字节,用vpshufb 对 128 位通道进行字节反转,然后用vextracti128 分别存储两半。 (或者做窄负载和宽存储。) 我之前尝试过,但没有成功。有没有比 x86 手册更好的文档可以指点我?我发现的一切都是 C++ 固有的做法,手册本身并不是最清楚的。我对使用这个扩展很陌生。 另外,我非常感谢您的回复。 您可以编写内在函数并查看编译器输出。或者Intel的x86手册很清楚(felixcloutier.com/x86/pshufb)。不幸的是,这些天来,AVX512 版本的所有内容都有些臃肿,因此请考虑查看 vol.2 的较旧 PDF。不过,Agner Fog 的优化组装指南很不错:agner.org/optimize 谢谢你。我非常感谢您提供的信息。 【参考方案1】:

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

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

或者使用额外的 shuffle 在 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 上,具有 dword 和更大粒度的广播负载与常规负载一样便宜。

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

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

【讨论】:

您的意思是 vinserti128 有 3 个参数吗?文档说我们需要 4 但不知道这是否是您在这种特殊情况下的意思的简写。 @kr1tzb1tz:哦,对,这不是故意的。但是 NASM 语法确实允许在与目标相同时省略第一个源,所以我认为如果你组装了我的原始版本,无论如何你都会得到这个。

以上是关于使用 x64 SSE / AVX 寄存器进行字符串反转的主要内容,如果未能解决你的问题,请参考以下文章

xmm 寄存器 sse x64 里面的值

是否有可能在 AVX/SSE 中获得多个正弦波?

使用 AVX/AVX2/SSE __m128i 将所有负数字节设置为 -128 (0x80) 并保留所有其他字节

AVX 或 SSE 上的水平尾随最大值

C++ 中 SSE/AVX 的 x86 CPU 调度

用于灰度到 ARGB 转换的 C++ SSE2 或 AVX2 内在函数