c++ - 获得 8 个源 __m256 vector 的水平和的 __m256 的最有效方法

标签 c++ matrix sum sse avx

我知道如何求和__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快如单vextractf128vinsertf128在英特尔 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 shufpsaddps 生成操作数.

如果我们可以效仿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/

相关文章:

c++ - 通过c/c++程序连接wifi

C++ winapi 提升

c++ - upper_bound 和 lower_bound 取值要求不一致

C++ opencv randu 函数抛出 'Integer division by zero'

python - 同情 : creating a numpy function from diagonal matrix that takes a numpy array

c++ - 具有多态性和不同数据结构的程序设计

C++矩阵计算

mysql - 从第一个表中选择所有内容并使用外键添加特定列的行

matlab - 在matlab中不使用for循环对4D矩阵求和

SQL Server Case 语句和聚合函数