_mm_extract_epi8(...) 以非文字整数作为参数的内在函数

Posted

技术标签:

【中文标题】_mm_extract_epi8(...) 以非文字整数作为参数的内在函数【英文标题】:_mm_extract_epi8(...) intrinsic that takes a non-literal integer as argument 【发布时间】:2012-10-16 11:13:32 【问题描述】:

我最近一直在使用 SSE 内在 int _mm_extract_epi8 (__m128i src, const int ndx),根据参考“从索引选择的压缩整数数组元素中提取一个整数字节”。这正是我想要的。

但是,我通过_m128i 上的_mm_cmpestri 确定索引,该索引执行具有显式长度的字符串数据的打包比较并生成索引。该索引的范围是 0..16,其中 0..15 表示有效索引,16 表示未找到索引。现在要提取索引位置处的整数,我想执行以下操作:

const int index = _mm_cmpestri(...);
if (index >= 0 && index < 16) 
  int intAtIndex = _mm_extract_epi8(..., index);

这给我们留下了 gcc (-O0) 编译器错误:

错误:选择器必须是 0..15 范围内的整数常量

解决这个问题的一个讨厌的方法是在索引上有一个switch,并为0..15 范围内的每个索引调用一个_mm_extract_epi8。我的问题是是否有我看不到的更好/更好的方式。

更新:使用-O3优化,没有编译错误;不过仍然是 -O0。

【问题讨论】:

你可以从你输入 _mm_cmpestri 的同一个 16 字节向量中加载字节 您从pcmpestri 获得索引。 pextrb 需要一个实际常量作为索引,而不是“好吧,我说这是一个常量,但实际上你不能将它编码为立即操作数”,那么它是如何工作的呢? @Maratyszcza:是的,你可以,但是使用开关和 _mm_extract_epi8 比访问源更快。我假设是因为 _m128i 已经指向加载的寄存器。 @muehlbau 好吧,您可以使用 pshufb 将第 index 个元素洗牌,然后取第 0 个元素。或者您可以生成一个掩码而不是索引,并将其与数据一起生成,然后由于它只是一个字节,您可以使用psadbw(其他参数应该为零)获取水平总和以将该字节置于零位置.他们都是丑陋的黑客,但是,你不应该真的想要这个。 您将__m128i 类型与char[16] 联合起来。然后按索引访问 char 数组。也就是说,通常不建议这样做,因为它很慢。但它可能仍然比 switch 语句更好。 【参考方案1】:

只是为了总结和结束问题。

我们讨论了 3 个选项,用于从 _m128i sse 中提取 [0..15] 中索引 i 处的字节,其中 i 在编译时无法简化为文字:

1) Switch & _mm_extract_epi8:在 i 上拥有一个 switch,并为 [0..15] 中的每个 i 提供一个 _mm_extract_epi8(sse,i) 的案例;像我现在一样工作是编译时文字。

2) Union hack:拥有一个union SSE128i __m128i sse; char[16] array; ,将其初始化为SSE128i sse = _mm_loadu_si128(...) ,并使用sse.array[i]访问索引i处的字节。

3) 将第 i 个元素打乱到位置 0 和 _mm_extract_epi8:使用 _mm_shuffle_epi8(sse,_mm_set1_epi8(i)) 将第 i 个元素打乱到位置 0;用_mm_extract_epi8(sse,0)提取它。

评估:我在 Intel Sandy Bridge 和 AMD Bulldozer 架构上对三个选项进行了基准测试。切换选项以微弱优势获胜。如果有人感兴趣,我可以发布更详细的数字和基准设置。

更新:评估 基准设置:解析 1GB 文件的每个字节。对于某些特殊字节,增加一个计数器。使用_mm_cmpistri 查找特殊字节的索引;然后使用提到的三种方法之一“提取”字节,并区分计数器增加的情况。代码是使用带有-std=c++0x -O3 -march=native 的 GCC 4.6 编译的。

对于每种方法,基准测试在 Sandy Bridge 机器上运行 25 次。结果(以秒为单位的运行时间的平均值和标准偏差):

切换和提取: 平均值:1071.45 标准差:2.72006

联合黑客: 平均值:1078.61 标准差:2.87131

从位置 0 随机和提取: 平均值:1079.32 标准差:2.69808

差异很小。我还没有机会查看生成的 asm。不过,看到差异可能会很有趣。目前我无法发布基准测试的完整代码,因为它包含非公开来源。如果我有时间,我会提取这些并发​​布来源。

【讨论】:

刚刚将它们添加到答案中。 我不知道,我对那个基准不太满意——你能用更少的数据来做吗?说,半L3大小?应该更清楚地显示差异。 由于内存中已经有了最初加载向量的字节,最好的办法可能是再次索引它,而不是鼓励编译器使用联合存储/重新加载 __m128i .您的 switch 测试是否使用相同的索引重复运行,因此它可以很好地进行分支预测?当您比较有分支与无分支时,微基准测试通常不能反映现实,除非您真的使用较长的模式长度随机化您的微基准。 相关:How to use _mm_extract_epi8 function?/ 如果您要循环所有元素,那么您肯定只想将这么多元素存储到一个数组中。

以上是关于_mm_extract_epi8(...) 以非文字整数作为参数的内在函数的主要内容,如果未能解决你的问题,请参考以下文章

如何使用非立即输入进行类似于 _mm_extract_epi8 的操作?

Python(42)_文件操作

开源代码删除图像中的非文本区域?

错误 Gradle Build C:\Program Files\Java\jdk1.7.0_79\bin\java.exe'' 以非零退出值 1 结束

使用Postgresql以非超级用户身份创建强制转换

如何在 SSE 中使用 imm8?