This question already has answers here:
Computing 8 horizontal sums of eight AVX single-precision floating-point vectors
(2个答案)
在10个月前关闭。
我知道如何对一个
输入值
输出量
我的方法好奇是否有更好的方法。
这compiles as you'd expect。第二个
源代码行的节省量大于指令的节省量,因为
这两个
不过,我们确实从完全消除了最终的
如果我们可以用一个
我认为以另一种方式进行水平求和似乎没有希望。 Normally, hadd is not a good choice,因为常见的水平和用例只关心其输出的一个元素。这里不是这种情况:每个
(2个答案)
在10个月前关闭。
我知道如何对一个
__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
与Intel CPU上的单个vextractf128
或vinsertf128
一样快。不过,在AMD上速度很慢(Bulldozer系列为8 m-op,延迟为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
相同的方式使用每个输入的低通道。 gcc 4.7和更高版本进行了此优化,但是只有最新的clang版本(v3.7)才进行此优化。如果您关心旧的 clang ,请在源代码级别执行此操作。源代码行的节省量大于指令的节省量,因为
_mm256_extractf128_ps(sumabcd, 0);
会编译为零个指令:这只是强制转换。除了vextractf128
之外,任何编译器都不得使用imm8发出1
。 (vmovdqa xmm/m128, xmm
总是最好用于获得低车道)。英特尔在浪费您无法使用的将来验证指令字节方面做得很好,因为纯VEX前缀没有空间编码更长的 vector 。这两个
vaddps xmm
指令可以并行运行,因此使用单个vaddps ymm
大多数情况下只是提高吞吐量(和代码大小),而不是延迟。不过,我们确实从完全消除了最终的
vinsertf128
减少了3个周期。vhaddps
是3微码,5c延迟和每2c吞吐量一个。 (Skylake上的6c延迟)。这三个ouop中的两个在shuffle端口上运行。我猜它基本上在做2倍shufps
来生成addps
的操作数。如果我们可以用一个
haddps
/ shufps
或类似的东西来仿真addps
(或者至少可以使用水平操作),那么我们会领先一步。不幸的是,我不知道如何。单个混洗只能使用来自两个 vector 的数据产生一个结果,但是我们需要垂直addps
的两个输入都具有来自两个 vector 的数据。我认为以另一种方式进行水平求和似乎没有希望。 Normally, hadd is not a good choice,因为常见的水平和用例只关心其输出的一个元素。这里不是这种情况:每个
hadd
结果的每个元素都被实际使用。10-06 01:41