获取16或32字节固定大小缓冲区的C字符串长度? (XMM或YMM寄存器宽度)

时间:2019-06-05 02:43:09

标签: assembly x86 sse strlen avx2

是否有任何方法可以通过将其加载到XMM或YMM寄存器中来获取存储在16字节或32字节缓冲区中的ASCII字符串的长度? 本质上,我正在寻找第一个零字节的索引(以位或字节为单位)。

我的目标是避免循环和分支。我希望AVX或SSE沿BSF(正向位扫描)的界线存在某些问题,但可以对字节而不是位进行操作。

也许类似以下内容?

_my_constant_time_strlen:
 vpxor ymm0, ymm0
 VPCMPEQB ymm0, ymm0, [rdi]
 vpmovmskb eax, ymm0
 bsf eax, eax
 ; string length is in eax?

   ; and rax, 31              ; editor's note: useless AND
 ret

1 个答案:

答案 0 :(得分:7)

正是您使用AVX2实现strlenmemchr的方式。(对于固定大小的缓冲区,您知道会有缓冲区中的某个匹配项。)

(除了现在,您有一个多余的and)。

vpmovmskb将比较向量转换为字节比较结果的位图,您可以使用BSF搜索该位图。您的代码已经完全可以满足您的要求。

如果避免破坏vpxor,则可以将vpcmpeqb归零。

在Intel CPU(Haswell / Skylake)上,实际工作只有3个uops。vpmovmskb是1个微融合的uop,因为您避免了索引寻址模式。 tzcnt是1 uop,具有2到3个周期的延迟。 bsf在Intel或AMD CPU上为1 uop。 (tzcnt在Intel上也是1 uop)。在Intel bsfvpcmpeqb上有3个周期的延迟。

因此,在主流Intel上,从准备就绪的矢量加载数据到RAX长度的总延迟为1({tzcnt)+ 2或3(movmsk)+ 3(vpcmpeqb ymm)= 6或7个周期。这是无分支的,只是数据依赖性,所以这是很合理的。无论地址或数据位于关键路径上,这都不会计算负载使用延迟或存储转发延迟。 而且吞吐量是优秀的,在Intel上,每个时钟1个strlen(端口0和/或端口1上出现瓶颈)。

在AMD Ryzen上,vpmovmskb ymm为2微秒,延迟为2c。 tzcnt是2 oups(对于Port2),延迟为3c。 lzcnt是2微秒,延迟为2c。因此,总延迟= 7个周期,吞吐量是每2个周期1个瓶颈,这是movmsk吞吐量的瓶颈。 (Ryzen tzcnt是1 uop / 1c延迟;大概lzcnt是位反转+ pcmpeqb或类似的东西。)

来自https://agner.org/optimize/的数字。


没有一条指令会扫描XMM或YMM寄存器中的第一个非零字节,唯一有效的方法是pmovmskb / vmovd xmm0, eax 。如果您想在向量reg中使用字节位置而不是整数,则可以在末尾vpand

但是通常您想要将结果保存在整数寄存器中。


唯一想到的是水平矢量搜索/扫描而没有先 movmsk首先扫描到整数的是phminposuw,这不是您想要的。

或者1,2,4,8,16, ...的向量为vpsadbw的2的幂,然后0xff对字节进行水平加法。结果的最低设置位告诉该8字节块中前0的位置。

或者您可以执行log2(vector_length)个步骤,分别对下一个元素进行改组和屏蔽,因此您将得到一个矢量,其中仅输入中的前0个具有VPAND元素。然后0,1,2,3,4,...的向量为vpsadbwvpcmpeqb到hsum,唯一的非零元素将是字节位置。但是,如果出于某种原因确实希望将结果保存在XMM寄存器中,则与vpmovmskb / bsf / vmovd / tzcnt返回XMM寄存器相比,这要昂贵得多。


如果不确定是否有零字节,请使用bsf而不是32,因为它会为全零输入生成bsf(操作数大小)。 tzcnt在AMD上也较慢,因此实际上您应该始终在此处使用bsf

如果输入为零,

vpxor XMM将使目标保持不变。 (由AMD记录;英特尔说“未定义”,但仍然可以实现。)

和/或在位扫描后检查ZF以检测全零输入情况。 (tzcnt将CF设置为全零输入,并根据输出是否为零设置ZF。)


此外,将YMM(而不是map2_chr)的寄存器清零。在AMD上保存uop。