考虑double
个数字的排序(升序)数组。对于数值稳定性,数组应该总结,好像从开始到结束迭代它,在一些变量中累加总和。
如何使用AVX2有效地对其进行矢量化?
我已经研究了这个方法Fastest way to do horizontal vector sum with AVX instructions,但是将它扩展到一个数组(可能需要一些除法和征服方法)似乎相当棘手,同时通过确保小数字来保持浮点精度在将它们添加到更大的数字之前总结。
澄清1 :我认为应该可以总结前4个项目,然后将它们添加到接下来的4个项目的总和等等。我愿意为性能交换一些稳定性。但我更喜欢一种不会完全破坏稳定性的方法。
澄清2 :内存不应该成为瓶颈,因为阵列在L3缓存中(但不在L1 / L2缓存中,因为阵列的各个部分是从不同的线程填充的)。我不想求助Kahan求和,因为我认为这真的是重要的操作次数,而Kahan求和会增加约4次。
答案 0 :(得分:4)
如果您需要精确的和并行度,请使用Kahan求和或其他误差补偿技术让您重新排序总和(使用多个累加器进入SIMD向量元素步幅)。
正如Twofold fast summation - Evgeny Latkin指出的那样,如果你在内存带宽上遇到瓶颈,那么错误补偿的总和并不比最大性能总和慢得多,因为CPU具有大量的计算吞吐量 - 内存带宽瓶颈的并行总和
另请参阅(kahan summation avx
的Google搜索结果)
Re:你的想法:按顺序对4个数字组进行求和,可以隐藏FP-add延迟,并增加标量瓶颈,增加吞吐量。
在向量中进行水平求和需要进行大量的改组,因此这是一个潜在的瓶颈。您可以考虑加载a0 a1 a2 a3
,然后随机播放以获取a0+a1 x a2+a3 x
,然后(a0+a1) + (a2+a3)
。你有一个Ryzen,对吧?最后一步是vextractf128
下降到128b。这仍然是3个ADD uop和3个shuffle uops,但是指令比标量或128b向量少。
我建议在解决这个问题之前先看看Kahan总和。
答案 1 :(得分:0)
到目前为止,这是我的解决方案:
double SumVects(const __m256d* pv, size_t n) {
if(n == 0) return 0.0;
__m256d sum = pv[0];
if(n == 1) {
sum = _mm256_permute4x64_pd(sum, _MM_SHUFFLE(3, 1, 2, 0));
} else {
for(size_t i=1; i+1 < n; i++) {
sum = _mm256_hadd_pd(sum, pv[i]);
sum = _mm256_permute4x64_pd(sum, _MM_SHUFFLE(3, 1, 2, 0));
}
sum = _mm256_hadd_pd(sum, pv[n-1]);
}
const __m128d laneSums = _mm_hadd_pd(_mm256_extractf128_pd(sum, 1),
_mm256_castpd256_pd128(sum));
return laneSums.m128d_f64[0] + laneSums.m128d_f64[1];
}
一些解释:它首先添加相邻的double
数组项,例如a[0]+a[1]
,a[2]+a[3]
等。然后它会添加相邻项的总和。
答案 2 :(得分:0)
你想玩的游戏可能适得其反。尝试通过从您最喜爱的发行版中生成一堆iid样本进行实验,对它们进行排序,然后按照递增的顺序进行比较&#34;使用&#34;按递增顺序对每个通道求和,然后对通道总和求和。&#34;
对于4个车道和16个数据,通过车道求和在28%的时间内给出较小的误差,而按递增顺序求和使得我在约17%的时间内出现较小的误差;对于4个通道和256个数据,通过Lanewise求和在68%的时间内给出较小的误差,而按递增顺序求和使得我在大约12%的时间内得到较小的误差。对Lanewise进行求和也比你在自我答案中给出的算法更胜一筹。为此我在[0,1]上使用了均匀分布。