在数组中查找零并切换标志的循环的SSE优化+;更新另一个数组 一段C++代码决定了零的出现,并为每个被检查的数字保留二进制标志变量。每次在一维数组中遇到零时,该标志的值在0和1之间切换

在数组中查找零并切换标志的循环的SSE优化+;更新另一个数组 一段C++代码决定了零的出现,并为每个被检查的数字保留二进制标志变量。每次在一维数组中遇到零时,该标志的值在0和1之间切换,c++,optimization,x86,sse,simd,C++,Optimization,X86,Sse,Simd,我正在尝试使用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来加快速度,但我不确定如何进行。据我所知,评估_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
     }
}
int标志=0;
int var_num2[1000];

对于(inti=0;i你必须认识到这个问题,但这是一个著名问题的变体。我首先给出一个理论描述

引入一个临时数组
not_var[]
,如果
var
包含
0
,则该数组包含
1
,否则为
0
。 引入一个临时数组
not\u var\u sum[]
,它保存
not\u var
的值。
var\u num2
现在是
非var\u sum[]的LSB

第一个和第三个操作可以简单地并行化。部分和的并行化非常简单


在实际实现中,您不会构造
not_var[]
,而是在步骤2的所有迭代中直接将LSB写入
var_num2
。这是有效的,因为您可以丢弃较高的位。仅保留LSB相当于取结果模2,
(a+b)%2=((a%2)+(b%2))%s

什么类型的
var[]
元素?
int
?或
char
?经常出现零

SIMD aka partial是可能的(例如,对于4
float
的向量,2次洗牌和2次加法),但基于结果的条件存储是另一个主要问题。(1000个元素的数组可能太小,多线程无法盈利。)

整数前缀和更容易有效地实现,整数运算的延迟更低。这不仅仅是不带进位的加法,即XOR,所以使用
\u mm\u XOR\u si128
而不是
\u mm\u add\u ps
(您将在
\u mm\u cmpeq\u epi32
中的整数全零/全一比较结果向量上使用它)(或
epi8
或其他,取决于
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;
}
for(大小j=0;j
如果索引提前,您可以生成整个列表,或者您可以分批工作,将搜索工作与存储工作重叠

问题的前缀和部分是通过在展开循环中交替存储0和1来处理的。真正的技巧是避免分支预测失误,并高效地生成索引

要生成
scatter\u element[]
,您已将问题转化为(过滤)一个基于相应
\u mm\u cmpeq\u epi32(var[i..i+3],\u mm\u setzero\u epi32())的(隐式)索引数组。
要生成要过滤的索引,请从
[0,1,2,3]
的向量开始,然后添加
[4,4]
\u mm\u add\u epi32
)。我假设
var[]
的元素大小为32位。如果元素较小,则需要解包

顺便说一句,AVX512有分散指令,您可以在这里使用,否则使用标量代码执行存储部分是最好的选择。(但是,在仅存储而不加载时要小心。)

为了将左打包与存储重叠,我认为您需要左打包,直到一个缓冲区中可能有64个索引。然后离开该循环,运行另一个左打包索引并使用索引的循环,仅当循环缓冲区已满(然后仅存储)或为空(然后仅左打包)时停止。这允许您将向量比较/查找表工作与分散存储工作重叠,但不会出现太多不可预测的分支


<0 >如果0非常频繁,并且<代码> VARYNUM2[]/Case>元素为32或64位,并且具有AVX或AVX2可用性,则可以考虑使用标准前缀和使用AVX屏蔽存储。例如,不使用SSE,但是:它具有NT提示,因此它绕过和从高速缓存中驱逐数据,并且非常慢。
此外,由于前缀和是mod 2,即布尔值,因此可以使用基于压缩比较结果掩码的查找表。不使用带洗牌的水平运算,而是使用比较的4位
movmskps
结果+初始状态的第5位作为32个向量的查找表的索引(假设
var[]的32位元素大小)
)。

试图比“不”更快在可能已经在寄存器中的变量上,…是乐观的。如果输入是
var
,那么
var\u num2
在实现中的作用是什么?@Codor
var\u num2
稍后在实现中使用。@Mat,你会怎么做呢?不访问u m128i变量的各个字段。算法是顺序的本质上是tial;我怀疑使用SSE是否可以加快速度。你的意思是“和
1
否则”?@Codor:No.在代码中,
不是var[I]=(var[I]==0)?:1:0
这是个好主意,但关键是变量
标志
的初始值是由一个依赖于不同变量的条件设置的。一旦设置好,它就需要切换。@Blue:次要细节,只需反转输出。要清楚:这只是Comp.Sci方法,通过通常的过程将其减少为已解决的pr问题。@Blue:如果
var[]
中的零很少出现,那么您可能会使用一种乐观的单通算法