一段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内在函数来解决这个问题?
答案 0 :(得分:3)
您必须认识到这个问题,但这是一个众所周知的问题的变体。我首先给出一个理论描述
引入一个临时数组not_var[]
,如果1
包含var
,则0
包含0
。
引入一个临时数组not_var_sum[]
,其中包含not_var
var_num2
。
not_var_sum[]
现在是not_var[]
第一个和第三个操作可以简单地并行化。并行部分和是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位。如果你有较小的元素,这需要解压缩。
要将左包装与存储重叠,我想你想要左包装,直到缓冲区中有64个索引。然后离开该循环并运行另一个循环,左包索引和使用索引,仅在循环缓冲区已满(然后只是存储)或空(然后只是左包)时停止。这使您可以将矢量比较/查找表工作与分散存储工作重叠,但不会有太多不可预测的分支。
如果零非常频繁且var[]
元素为32或64位,并且您有AVX或AVX2可用,则可以考虑使用标准前缀和并使用AVX蒙版存储。例如vpmaskmovd
。但是,不要使用SSE maskmovdqu
:它有一个NT提示,因此它绕过并从缓存中驱逐数据,并且速度很慢。
另外,因为你的前缀sum是mod 2,即boolean,你可以使用基于打包比较结果掩码的查找表。使用比特的4位var_num2[]
结果和初始状态的第5位作为32个向量的查找表的索引(假设{{1的32位元素大小)而不是带有混洗的水平操作}})。