用另一个替换一个字节

sen*_*iwa 0 sse simd avx

我发现在为这个看似简单的问题创建代码时遇到了困难.

给定一个打包的8位整数,如果存在则用另一个字节替换另一个字节

举例来说,我想替换0x060x01,所以我可以做以下res的输入找到0x06:

// Bytes to be manipulated
res = _mm_set_epi8(0x00, 0x03, 0x02, 0x06, 0x0F, 0x02, 0x02, 0x06, 0x0A, 0x03, 0x02, 0x06, 0x00, 0x00, 0x02, 0x06);

// Target value and substitution
val = _mm_set1_epi8(0x06);
sub = _mm_set1_epi8(0x01);

// Find the target
sse = _mm_cmpeq_epi8(res, val);

// Isolate target
sse = _mm_and_si128(res, sse);

// Isolate remaining bytes
adj = _mm_andnot_si128(sse, res);
Run Code Online (Sandbox Code Playgroud)

现在我不知道如何继续or这两个部分,我需要删除目标并用替换的字节替换它.

我在这里缺少什么SIMD指令?

和其他问题一样,我只限于AVX,我没有更好的处理器.

cht*_*htz 6

您基本上需要做的是将所有要替换的字节(输入)设置为零.然后将替换的所有其他字节设置为零并对结果进行OR运算.你已经有了一个面具来做_mm_cmpeq_epi8.总的来说,这可以这样做:

__m128i mask = _mm_cmpeq_epi8(inp, val);
return _mm_or_si128(_mm_and_si128(mask, sub), _mm_andnot_si128(mask, inp));
Run Code Online (Sandbox Code Playgroud)

由于和/和/或的最后一个组合是非常常见的,SSE4.1引入了一条指令(基本上)将它们组合成一个:

__m128i mask = _mm_cmpeq_epi8(inp, val);
return _mm_blendv_epi8(inp, sub, mask);
Run Code Online (Sandbox Code Playgroud)

实际上,当使用优化编译时,clang5.0及更高版本足够聪明,可以用第二个替换第一个变体:https://godbolt.org/z/P-tcik


注意:如果替换值实际上是0x01你可以利用掩码(比较结果)是0x000xff(即-0x01)的事实,即你可以将你要替换的值清零,然后减去掩码:

__m128i val = _mm_set1_epi8(0x06);
__m128i mask = _mm_cmpeq_epi8(inp, val);
return _mm_sub_epi8(_mm_andnot_si128(mask, inp), mask);
Run Code Online (Sandbox Code Playgroud)

这可以节省0x01从内存加载向量或浪费寄存器.根据您的架构,它可能会略微提高吞吐量.

  • 使用`sub_epi8`代替`和`/`或`的好方法(但不幸的是仍然不能保存MOVDQA,所以`pblendvb`仍然更好,特别是在Skylake和更新,尤其是非AVX版本为1 uop对于任何港口).其他特殊情况是替换= 0xff:`OR(inp,mask)`因为对于任何x都是`x | 0xFF = 0xFF`,当然替换= 0,你可以只用AND而不是混合.(对于像ADD和XOR这样的操作,`0`是标识值,所以你可以屏蔽输入到'a + b`而不是混合输出.)Clang会为你找到一些,它的SIMD优化器非常棒. (3认同)