对于在数组中找到零并切换标志+更新另一个数组的循环的SSE优化

时间:2017-12-18 09:25:17

标签: c++ optimization x86 sse simd

一段C ++代码确定零的出现并为每个被检查的数字保留二进制标志变量。每次在1维数组中遇到零时,标志的值在0和1之间切换。

我正在尝试使用SSE来加速它,但我不确定如何解决这个问题。评估__m128i的各个字段是低效的,我已经阅读过了。

C ++中的代码是:

int flag = 0;
int var_num2[1000];
for(int i = 0; i<1000; i++)
{  
    if (var[i] == 0)
    {
        var_num2[i] = flag;
        flag = !flag;  //toggle value upon encountering a 0
     }
}

我应该如何使用SSE内在函数来解决这个问题?

2 个答案:

答案 0 :(得分:3)

您必须认识到这个问题,但这是一个众所周知的问题的变体。我首先给出一个理论描述

引入一个临时数组not_var[],如果1包含var,则0包含0。 引入一个临时数组not_var_sum[],其中包含not_var var_num2not_var_sum[]现在是not_var[]

的LSB

第一个和第三个操作可以简单地并行化。并行部分和是partial sum

在实际实现中,您不会构造var_num2,并且您在步骤2的所有迭代中将LSB直接写入(a+b)%2 == ((a%2) + (b%2))%s。这是有效的,因为您可以丢弃更高的位。仅保留LSB相当于以模2和{ "_id":"1", "company":"ABCD", "Address":{ "Location":"XYZ" }, "empName":[{ "ID":1, "Name":"test1" }, "ID":2, "Name":"test2" }, "ID":3, "Name":"test3" }] } 取结果。

答案 1 :(得分:0)

var[]的元素是什么类型的? int?还是char?零是否经常发生?

SIMD prefix sum也称为部分可能(with log2(vector_width) work per element,例如2个shuffle和4个float向量的2个添加,但基于结果的条件存储是其他主要问题。 (您的1000个元素的数组可能太小,无法使多线程获利。)

整数前缀和更容易有效,并且整数操作的较低延迟有帮助。 NOT只是在没有进位的情况下添加,即XOR,因此请使用_mm_xor_si128代替_mm_add_ps。 (您将在_mm_cmpeq_epi32(或epi8或其他任何内容的整数全0 /全1比较结果向量上使用此值,具体取决于var[]的元素大小。你没有指定,但不同规模的战略选择可能是最佳的。)

但是,仅仅拥有SIMD前缀总和实际上几乎没有帮助:您仍然需要循环并找出存储位置和未经修改的位置。

我认为最好的办法是生成一个需要存储的索引列表,然后

for (size_t j = 0 ; j < scatter_count ; j+=2) {
    var_num2[ scatter_element[j+0] ] = 0;
    var_num2[ scatter_element[j+1] ] = 1;
}

如果索引是预先生成的,您可以生成整个列表,或者您可以小批量工作以将搜索工作与商店工作重叠。

通过在展开的循环中交替存储0和1来处理问题的前缀和部分。真正的诀窍是避免分支错误预测,并有效地生成指数。

要生成scatter_element[],您已根据相应的{{1}将问题转换为 left-packing (过滤)一个(隐式)索引数组}。要生成您要过滤的索引,请从_mm_cmpeq_epi32( var[i..i+3], _mm_setzero_epi32() )向量开始,并向其添加[0,1,2,3][4,4,4,4])。我假设_mm_add_epi32的元素大小是32位。如果你有较小的元素,这需要解压缩。

BTW,AVX512有散点指令,你可以在这里使用,否则用标量代码做商店部分是你最好的选择。 (但是在没有加载的情况下存储时要小心Unexpectedly poor and weirdly bimodal performance for store loop on Intel Skylake。)

要将左包装与存储重叠,我想你想要左包装,直到缓冲区中有64个索引。然后离开该循环并运行另一个循环,左包索引使用索引,仅在循环缓冲区已满(然后只是存储)或空(然后只是左包)时停止。这使您可以将矢量比较/查找表工作与分散存储工作重叠,但不会有太多不可预测的分支。

如果零非常频繁且var[]元素为32或64位,并且您有AVX或AVX2可用,则可以考虑使用标准前缀和并使用AVX蒙版存储。例如vpmaskmovd。但是,不要使用SSE maskmovdqu:它有一个NT提示,因此它绕过并从缓存中驱逐数据,并且速度很慢。

另外,因为你的前缀sum是mod 2,即boolean,你可以使用基于打包比较结果掩码的查找表。使用比特的4位var_num2[]结果和初始状态的第5位作为32个向量的查找表的索引(假设{{1的32位元素大小)而不是带有混洗的水平操作}})。