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

问题描述 投票:3回答:2

一段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内在函数来解决这个问题?

c++ optimization x86 sse simd
2个回答
3
投票

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

如果not_var[]包含1var,则引入包含0的临时数组0。介绍一个临时阵列not_var_sum[],其中包含partial sumnot_varvar_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的结果。


0
投票

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。

© www.soinside.com 2019 - 2024. All rights reserved.