【问题标题】:Fastest way to do horizontal vector sum with AVX instructions [duplicate]使用 AVX 指令进行水平向量求和的最快方法 [重复]
【发布时间】:2012-04-04 06:07:32
【问题描述】:

我有一个包含四个 64 位浮点值的压缩向量。
我想得到向量元素的总和。

使用 SSE(并使用 32 位浮点数)我可以执行以下操作:

v_sum = _mm_hadd_ps(v_sum, v_sum);
v_sum = _mm_hadd_ps(v_sum, v_sum);

不幸的是,尽管 AVX 具有 _mm256_hadd_pd 指令,但它的结果与 SSE 版本不同。我相信这是因为大多数 AVX 指令分别作为每个低 128 位和高 128 位的 SSE 指令工作,而不会跨越 128 位边界。

理想情况下,我正在寻找的解决方案应遵循以下准则:
1) 只使用 AVX/AVX2 指令。 (无 SSE)
2) 在不超过 2-3 条指令内完成。

但是,任何有效/优雅的方式(即使不遵循上述准则)总是被广泛接受。

非常感谢您的帮助。

-路易吉·卡斯特利

【问题讨论】:

标签: x86 sse simd avx vector-processing


【解决方案1】:

如果您有两个 __m256d 向量 x1x2,每个向量都包含四个您想要水平求和的 doubles,您可以这样做:

__m256d x1, x2;
// calculate 4 two-element horizontal sums:
// lower 64 bits contain x1[0] + x1[1]
// next 64 bits contain x2[0] + x2[1]
// next 64 bits contain x1[2] + x1[3]
// next 64 bits contain x2[2] + x2[3]
__m256d sum = _mm256_hadd_pd(x1, x2);
// extract upper 128 bits of result
__m128d sum_high = _mm256_extractf128_pd(sum1, 1);
// add upper 128 bits of sum to its lower 128 bits
__m128d result = _mm_add_pd(sum_high, _mm256_castpd256_pd128(sum));
// lower 64 bits of result contain the sum of x1[0], x1[1], x1[2], x1[3]
// upper 64 bits of result contain the sum of x2[0], x2[1], x2[2], x2[3]

所以看起来 3 条指令将完成您需要的 2 条水平求和。以上内容未经测试,但您应该了解这个概念。

【讨论】:

  • 我认为您应该使用 _mm256_castpd256_pd128(sum) 而不是使用强制转换 (__m128d)。
  • 你用什么编译器来做这个?我在这里c-style-cast-versus-intrinsic-cast问了一个关于你的演员的问题。我现在无法访问系统来测试它。
  • @Zboson:我编辑了 sn-p 以使用更标准的转换方法。
  • 这在 Intel CPU 上效率较低,在 AMD CPU 上效率非常低。见Get sum of values stored in __m256d with SSE/AVX。没错,但问题要求“最快”,而不是简单或紧凑/小代码大小。
  • 我想我之前看错了。你正在做两个单独的水平总和。这在 Intel 上实际上很好,在 Zen 或 Zen2 上可能还可以。但如果您最终要将__m128d result 的两半相加,则不会。所以要充分利用这一点,你需要有两个独立的东西需要hsum。
【解决方案2】:

如果您只想要总和,并且可以接受一些标量代码:

__m256d x;
__m256d s = _mm256_hadd_pd(x,x);
return ((double*)&s)[0] + ((double*)&s)[2];

【讨论】:

  • 闻起来像那个演员未定义的行为。
【解决方案3】:

假设以下情况,您有一个包含 4 个压缩双精度的 __m256d 向量,并且您想要计算其分量的总和,即 a0, a1, a2, a3 是您想要的每个双分量 a0 + a1 + a2 + a3 然后这是另一个 AVX 解决方案:

// goal to calculate a0 + a1 + a2 + a3
__m256d values = _mm256_set_pd(23211.24, -123.421, 1224.123, 413.231);

// assuming _mm256_hadd_pd(a, b) == a0 + a1, b0 + b1, a2 + a3, b2 + b3 (5 cycles) ...
values = _mm256_hadd_pd(values, _mm256_permute2f128_pd(values, values, 1));
// ^^^^^^^^^^^^^^^^^^^^ a0 + a1, a2 + a3, a2 + a3, a0 + a1

values = _mm256_hadd_pd(values, values);
// ^^^^^^^^^^^^^^^^^^^^ (a0 + a1 + a2 + a3), (a0 + a1 + a2 + a3), (a2 + a3 + a0 + a1), (a2 + a3 + a0 + a1)

// Being that addition is associative then each component of values contains the sum of all its initial components (11 cycles) to calculate, (1-2 cycles) to extract, total (12-13 cycles)
double got = _mm_cvtsd_f64(_mm256_castpd256_pd128(values)), exp = (23211.24 + -123.421 + 1224.123 + 413.231);

if (got != exp || _mm256_movemask_pd(_mm256_cmp_pd(values, _mm256_set1_pd(exp), _CMP_EQ_OS)) != 0b1111)
    printf("Failed to sum double components, exp: %f, got %f\n", exp, got);
else
    printf("ok\n");

此解决方案已广播的总和可能有用...

如果我误解了问题,我深表歉意。

$ uname -a
Darwin Samys-MacBook-Pro.local 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix

【讨论】:

  • 如果你想要所有元素的结果总和,hadd 仍然不是最有效的方法。您只需要 1 次 shuffle + add,而不是 HADD 的 2x shuffle uops 喂一个 add。例如val = add(val, permute2f128(val,val 1)); 然后在 128 位通道内与 _mm256_shuffle_pd 交换以提供另一个添加。在 Intel 上总共只有 4 uops,与 Get sum of values stored in __m256d with SSE/AVX 相同
猜你喜欢
  • 2011-10-23
  • 2016-08-28
  • 2014-06-05
  • 1970-01-01
  • 2016-08-02
  • 2018-07-29
  • 1970-01-01
  • 2018-08-31
相关资源
最近更新 更多