使用 sse 和 avx 内在函数将一组打包的单曲添加到一个值中
Posted
技术标签:
【中文标题】使用 sse 和 avx 内在函数将一组打包的单曲添加到一个值中【英文标题】:Using sse and avx intrinsics to add a set of packed singles into one value 【发布时间】:2014-03-28 19:55:57 【问题描述】:我有代码要加快速度。首先,我使用了 SSE 内在函数并看到了显着的收益。我现在想看看我是否可以对 AVX 内在函数做类似的事情。本质上,该代码采用两个数组,根据需要对它们进行加减运算,将结果平方,然后将所有这些平方相加。
下面是使用 sse 内部函数的代码的简化版本:
float chiList[4] __attribute__((aligned(16)));
float chi = 0.0;
__m128 res;
__m128 nres;
__m128 del;
__m128 chiInter2;
__m128 chiInter;
while(runNum<boundary)
chiInter = _mm_setzero_ps();
for(int i=0; i<maxPts; i+=4)
//load the first batch of residuals and deltas
res = _mm_load_ps(resids+i);
del = _mm_load_ps(residDeltas[param]+i);
//subtract them
nres = _mm_sub_ps(res,del);
//load them back into memory
_mm_store_ps(resids+i,nres);
//square them and add them back to chi with the fused
//multiply and add instructions
chiInter = _mm_fmadd_ps(nres, nres, chiInter);
//add the 4 intermediate this way because testing
//shows it is faster than the commented out way below
//so chiInter2 has chiInter reversed
chiInter2 = _mm_shuffle_ps(chiInter,chiInter,_MM_SHUFFLE(0,1,2,3));
//add the two
_mm_store_ps(chiList,_mm_add_ps(chiInter,chiInter2));
//add again
chi=chiList[0]+chiList[1];
//now do stuff with the chi^2
//alternatively, the slow way
//_mm_store_ps(chiList,chiInter);
//chi=chiList[0]+chiList[1]+chiList[2]+chiList[3];
这让我想到了我的第一个问题:有什么方法可以更优雅地完成最后一点(我将 chiInter 中的 4 个浮点数相加并将它们加到一个浮点数中)? p>
无论如何,我现在正在尝试使用 avx 内在函数来实现这一点,这个过程的大部分过程都非常简单,不幸的是我正在拖延尝试做最后一点,试图将 8 个中间 chi 值压缩成一个值。
下面是 avx 内部函数的类似简化代码:
float chiList[8] __attribute__((aligned(32)));
__m256 res;
__m256 del;
__m256 nres;
__m256 chiInter;
while(runNum<boundary)
chiInter = _mm256_setzero_ps();
for(int i=0; i<maxPts; i+=8)
//load the first batch of residuals and deltas
res = _mm256_load_ps(resids+i);
del = _mm256_load_ps(residDeltas[param]+i);
//subtract them
nres = _mm256_sub_ps(res,del);
//load them back into memory
_mm256_store_ps(resids+i,nres);
//square them and add them back to chi with the fused
//multiply and add instructions
chiInter = _mm256_fmadd_ps(nres, nres, chiInter);
_mm256_store_ps(chiList,chiInter);
chi=chiList[0]+chiList[1]+chiList[2]+chiList[3]+
chiList[4]+chiList[5]+chiList[6]+chiList[7];
我的第二个问题是:有没有像我上面提到的 SSE 这样的方法可以让我更快地完成最后的添加?或者,如果有更好的方法来做我在 SSE 内在函数中所做的事情,它是否具有 AVX 内在函数的等价物?
【问题讨论】:
不要太担心最终总和的效率 - 假设maxPts
相当大,那么总时间将由 for 循环内发生的事情以及任何前导码/后置码控制代码将与性能无关。
@PaulR,不幸的是,maxPts 很小,通常不超过 32。是的,尽管尺寸很小,但我看到使用 sse 与 naive 循环的巨大收益,即 144ns / 迭代 --> 14ns / 迭代。
查看相关:***.com/q/9775538/1918193。我很惊讶你没有尝试使用 haddps。要搜索的关键字:水平加法/总和。
@MarcGlisse,我没有,因为当我通过它时我不知道我在看什么。非常感谢您提供的信息。如果你把它写成答案,我很乐意接受。
如果 maxPts 很大,您可能需要检查 chi
中最终累积误差平方的指数,浮点数(单精度浮点)只有 24 位有效位 en.wikipedia.org/wiki/Single-precision_floating-point_format 作为您的累积错误增加,您将失去精度,并且可能会达到停止进一步累积的程度(残差的中间平方增量可能比您运行累积的chi
小 2^24 倍,并且当 CPU 归一化以添加它们时归零。
【参考方案1】:
此操作称为水平求和。假设你有一个向量v=x0,x1,x2,x3,x4,x5,x6,x7
。首先,提取高/低部分,得到w1=x0,x1,x2,x3
和w2=x4,x5,x6,x7
。现在打电话给_mm_hadd_ps(w1, w2)
,得到:tmp1=x0+x1,x2+x3,x4+x5,x6+x7
。同样,_mm_hadd_ps(tmp1,tmp1)
给出了tmp2=x0+x1+x2+x3,x4+x5+x6+x7,...
。最后一次,_mm_hadd_ps(tmp2,tmp2)
给了tmp3=x0+x1+x2+x3+x4+x5+x6+x7,...
。您也可以将第一个 _mm_hadd_ps
替换为简单的 _mm_add_ps
。
这都是未经测试的,并且是从文档中编写的。也没有承诺速度......
Intel forum 上的某人显示了另一个变体(查找 HsumAvxFlt
)。
我们也可以通过gcc test.c -Ofast -mavx2 -S
编译这段代码来看看gcc的建议
float f(float*t)
t=(float*)__builtin_assume_aligned(t,32);
float r=0;
for(int i=0;i<8;i++)
r+=t[i];
return r;
生成的test.s
包含:
vhaddps %ymm0, %ymm0, %ymm0
vhaddps %ymm0, %ymm0, %ymm1
vperm2f128 $1, %ymm1, %ymm1, %ymm0
vaddps %ymm1, %ymm0, %ymm0
我有点惊讶最后一条指令不是vaddss
,但我想这并不重要。
【讨论】:
哇,这很有帮助。非常感谢你。我一直在通过搜索和测试来写我的改进,偶尔也会遇到一些事情。内在函数救了我,因为我真的不想编写内联汇编,但直到一周前我才知道它们。以上是关于使用 sse 和 avx 内在函数将一组打包的单曲添加到一个值中的主要内容,如果未能解决你的问题,请参考以下文章