对于在数组中找到零并切换标志+更新另一个数组的循环的SSE优化
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对于在数组中找到零并切换标志+更新另一个数组的循环的SSE优化相关的知识,希望对你有一定的参考价值。
一段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内在函数来解决这个问题?
你必须认识到这个问题,但这是一个众所周知的问题的变种。我先给出一个理论描述
如果not_var[]
包含1
和var
,则引入包含0
的临时数组0
。介绍一个临时阵列not_var_sum[]
,其中包含partial sum的not_var
。 var_num2
现在是not_var_sum[]
的LSB
第一和第三操作可以平行地并行化。并行化部分和是only a bit harder。
在实际实现中,您不会构造not_var[]
,并且您将在步骤2的所有迭代中将LSB直接写入var_num2
。这是有效的,因为您可以丢弃较高位。仅保留LSB相当于取模2和(a+b)%2 == ((a%2) + (b%2))%s
的结果。
var[]
的元素是什么类型的? int
?还是char
?零是频繁的吗?
SIMD prefix sum aka partial是可能的(with log2(vector_width) work per element,例如2个shuffles和4个float
的向量的2个添加),但基于结果的条件存储是另一个主要问题。 (您的1000个元素数组可能太小,无法使多线程获利。)
整数前缀和更容易有效地执行,并且整数操作的较低延迟有助于。 NOT只是在没有进位的情况下添加,即XOR,所以使用_mm_xor_si128
而不是_mm_add_ps
。 (你将在_mm_cmpeq_epi32
(或epi8
等等)的整数全零/全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[]
,你已经根据相应的left-packing将问题转换为_mm_cmpeq_epi32( var[i..i+3], _mm_setzero_epi32() )
(过滤)一个(隐式)索引数组。要生成您正在过滤的索引,请从[0,1,2,3]
的向量开始并向其添加[4,4,4,4]
(_mm_add_epi32
)。我假设var[]
的元素大小是32位。如果您有较小的元素,则需要拆包。
顺便说一句,AVX512有散点指令,你可以在这里使用,否则用标量代码做商店部分是你最好的选择。 (但是在没有加载的情况下存放时要小心Unexpectedly poor and weirdly bimodal performance for store loop on Intel Skylake。)
要将左包装与存储重叠,我想你想要左包装,直到你在缓冲区中有64个索引。然后离开该循环并运行另一个循环,左包装索引并使用索引,仅在循环缓冲区已满(然后只是存储)或空(然后只是左包装)时停止。这使您可以将矢量比较/查找表工作与分散存储工作重叠,但不会有太多不可预测的分支。
如果零是非常频繁的,并且var_num2[]
元素是32或64位,并且您有AVX或AVX2可用,您可以考虑使用标准前缀和并使用AVX蒙版存储。例如vpmaskmovd
。不要使用SSE maskmovdqu
:它有一个NT提示,因此它绕过并从缓存中驱逐数据,并且速度很慢。
此外,因为您的前缀sum是mod 2,即boolean,您可以使用基于打包比较结果掩码的查找表。使用比特的4位movmskps
结果和初始状态的第5位作为32个向量的查找表的索引(假设var[]
为32位元素大小),而不是带有shuffle的水平ops。
以上是关于对于在数组中找到零并切换标志+更新另一个数组的循环的SSE优化的主要内容,如果未能解决你的问题,请参考以下文章
Numpy:对于一个数组中的每个元素,找到另一个数组中的索引
尝试根据值设置不同的数组。错误:组件渲染函数中可能存在无限更新循环