我知道如何求和__m256
获得单个求和值。然而,我有 8 个 vector ,例如
输入
1: a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
.....,
.....,
8: h[0], h[1], h[2], h[3], h[4], a[5], a[6], a[7]
输出
a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7],
....,
h[0]+h[1]+h[2]+h[3]+h[4]+h[5]+h[6]+h[7]
我的方法。好奇是否有更好的方法。
__m256 sumab = _mm256_hadd_ps(accumulator1, accumulator2);
__m256 sumcd = _mm256_hadd_ps(accumulator3, accumulator4);
__m256 sumef = _mm256_hadd_ps(accumulator5, accumulator6);
__m256 sumgh = _mm256_hadd_ps(accumulator7, accumulator8);
__m256 sumabcd = _mm256_hadd_ps(sumab, sumcd);
__m256 sumefgh = _mm256_hadd_ps(sumef, sumgh);
__m128 sumabcd1 = _mm256_extractf128_ps(sumabcd, 0);
__m128 sumabcd2 = _mm256_extractf128_ps(sumabcd, 1);
__m128 sumefgh1 = _mm256_extractf128_ps(sumefgh, 0);
__m128 sumefgh2 = _mm256_extractf128_ps(sumefgh, 1);
sumabcd1 = _mm_add_ps(sumabcd1, sumabcd2);
sumefgh1 = _mm_add_ps(sumefgh1, sumefgh2);
__m256 result =_mm256_insertf128_ps(_mm256_castps128_ps256(sumabcd1), sumefgh1, 1)
最佳答案
更新:Computing 8 horizontal sums of eight AVX single-precision floating-point vectors (我认为)是同样的问题,用一个混合替换其中一个 _mm256_permute2f128_ps 来解决。另一个答案是用更多混合代替洗牌微指令。请改用其中之一。
原始答案未能使用任何混合,并且会在随机播放时出现瓶颈
您可以使用 2x _mm256_permute2f128_ps
将低车道和高车道排列成垂直vaddps
。这不是 2x extractf128
/insertf128
。这也变成了两个 128b vaddps xmm
指令转换为单个 256b vaddps ymm
.
vperm2f128
快如单vextractf128
或vinsertf128
在英特尔 CPU 上。不过,在 AMD 上速度很慢(Bulldozer 系列上有 8 m-ops,延迟为 4c)。不过,即使您关心 AMD 的性能,也还不错,需要避免它。 (其中一个排列实际上可以是 vinsertf128
)。
__m256 hsum8(__m256 a, __m256 b, __m256 c, __m256 d,
__m256 e, __m256 f, __m256 g, __m256 h)
{
// a = [ A7 A6 A5 A4 | A3 A2 A1 A0 ]
__m256 sumab = _mm256_hadd_ps(a, b);
__m256 sumcd = _mm256_hadd_ps(c, d);
__m256 sumef = _mm256_hadd_ps(e, f);
__m256 sumgh = _mm256_hadd_ps(g, h);
__m256 sumabcd = _mm256_hadd_ps(sumab, sumcd); // [ D7:4 ... A7:4 | D3:0 ... A3:0 ]
__m256 sumefgh = _mm256_hadd_ps(sumef, sumgh); // [ H7:4 ... E7:4 | H3:0 ... E3:0 ]
__m256 sum_hi = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x31); // [ H7:4 ... E7:4 | D7:4 ... A7:4 ]
__m256 sum_lo = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x20); // [ H3:0 ... E3:0 | D3:0 ... A3:0 ]
__m256 result = _mm256_add_ps(sum_hi, sum_lo);
return result;
}
这个compiles as you'd expect 。第二个permute2f128
实际上编译为 vinsertf128
,因为它仅以与 vinsertf128
相同的方式使用每个输入的低 channel 。做。 gcc 4.7 及更高版本执行此优化,但只有更新的 clang 版本才会执行此优化 (v3.7)。如果您关心旧的 clang,请在源代码级别执行此操作。
源代码行的节省大于指令的节省,因为_mm256_extractf128_ps(sumabcd, 0);
编译为零指令:这只是一个强制转换。任何编译器都不应该发出 vextractf128
具有除 1
之外的 imm8 。 (vmovdqa xmm/m128, xmm
总是更适合进入低车道)。干得好,英特尔浪费了一个指令字节来保证您无法使用,因为普通的 VEX 前缀没有空间来编码更长的 vector 。
两个vaddps xmm
指令可以并行运行,因此使用单个 vaddps ymm
主要只是吞吐量(和代码大小)增益,而不是延迟。
我们确实缩短了 3 个周期,从而完全消除了最终的 vinsertf128
不过。
vhaddps
是 3 uop,5c 延迟,每 2c 吞吐量 1 个。 (Skylake 上的延迟为 6c)。这三个微指令中的两个在随机端口上运行。我猜它基本上是做 2x shufps
为 addps
生成操作数.
如果我们可以效仿haddps
(或者至少获得我们可以使用的水平操作)与单个 shufps
/addps
或者其他什么,我们会领先。不幸的是,我不知道如何。一次洗牌只能用来自两个 vector 的数据产生一个结果,但我们需要垂直 addps
的两个输入从两个 vector 中获取数据。
我认为以另一种方式进行水平求和看起来不太有希望。 Normally, hadd is not a good choice ,因为常见的水平求和用例只关心其输出的一个元素。这里的情况并非如此:每个 hadd
的每个元素结果被实际使用。
关于c++ - 获得 8 个源 __m256 vector 的水平和的 __m256 的最有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36195356/