【问题标题】:Horizontal sum of 32-bit floats in 256-bit AVX vector [duplicate]256位AVX向量中32位浮点数的水平总和[重复]
【发布时间】:2014-06-05 01:13:17
【问题描述】:

我有两个浮点数组,我想使用 SSE 和 AVX 以尽可能低的延迟计算点积。我知道浮点数固有一个 256 位点积,但我在 SO 上读到这比以下技术慢:(https://stackoverflow.com/a/4121295/997112)。

我已经完成了大部分工作,向量 temp_sums 包含所有的和,我只需要将 temp_sum 中包含的所有 8 个 32 位和最后求和即可。

#include "xmmintrin.h"
#include "immintrin.h"

int main(){
    const int num_elements_in_array = 16;
    __declspec(align(32)) float x[num_elements_in_array];
    __declspec(align(32)) float y[num_elements_in_array];

    x[0] = 2;   x[1] = 2;   x[2] = 2;   x[3] = 2;
    x[4] = 2;   x[5] = 2;   x[6] = 2;   x[7] = 2;
    x[8] = 2;   x[9] = 2;   x[10] = 2;  x[11] = 2;
    x[12] = 2;  x[13] = 2;  x[14] = 2;  x[15] = 2;

    y[0] = 3;   y[1] = 3;   y[2] = 3;   y[3] = 3;
    y[4] = 3;   y[5] = 3;   y[6] = 3;   y[7] = 3;
    y[8] = 3;   y[9] = 3;   y[10] = 3;  y[11] = 3;
    y[12] = 3;  y[13] = 3;  y[14] = 3;  y[15] = 3;

    __m256 a;
    __m256 b;
    __m256 temp_products;   
    __m256 temp_sum = _mm256_setzero_ps();

    unsigned short j = 0;
    const int sse_data_size = 32;
    int num_values_to_process = sse_data_size/sizeof(float);

    while(j < num_elements_in_array){
        a = _mm256_load_ps(x+j);
        b = _mm256_load_ps(y+j);

        temp_products = _mm256_mul_ps(b, a);
        temp_sum = _mm256_add_ps(temp_sum, temp_products);

        j = j + num_values_to_process;
    }

    //Need to "process" temp_sum as a final value here

}

我担心我需要的 256 位内在函数在 AVX 1 之前不可用。

【问题讨论】:

标签: c++ vectorization sse simd avx


【解决方案1】:

我建议尽可能使用 128 位 AVX 指令。它将减少一次跨域 shuffle 的延迟(在 Intel Sandy/Ivy Bridge 上为 2 个周期)并提高在 128 位执行单元(当前为 AMD Bulldozer、Piledriver、Steamroller 和 Jaguar)上运行 AVX 指令的 CPU 的效率:

static inline float _mm256_reduce_add_ps(__m256 x) {
    /* ( x3+x7, x2+x6, x1+x5, x0+x4 ) */
    const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(x, 1), _mm256_castps256_ps128(x));
    /* ( -, -, x1+x3+x5+x7, x0+x2+x4+x6 ) */
    const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));
    /* ( -, -, -, x0+x1+x2+x3+x4+x5+x6+x7 ) */
    const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));
    /* Conversion to float is a no-op on x86-64 */
    return _mm_cvtss_f32(x32);
}

【讨论】:

  • _mm_cvtf128_f32 是否正确?我在英特尔内在指南上看不到它:software.intel.com/sites/landingpage/IntrinsicsGuide
  • 是的,所有主要编译器都支持它(iccgccclangmsvc
  • 你说得对,内在函数应该叫_mm_cvtss_f32
  • @Zboson On Bulldozer 由于指令解码器的缺陷,AVX-256 通常不如 AVX-128 高效。在其他处理器上,尽管 AVX-256 指令在内部分解为 2 个微操作,但由于对指令解码器的压力较小(通常是瓶颈),因此 AVX 效率更高。
  • 会,但 128 位 SSE 内在函数会生成 128 位 AVX 指令,而不是针对 AVX 指令集时的 SSE 指令
【解决方案2】:

您可以使用 AVX 模拟完整的水平添加(即 _mm256_hadd_ps 的正确 256 位版本),如下所示:

#define _mm256_full_hadd_ps(v0, v1) \
        _mm256_hadd_ps(_mm256_permute2f128_ps(v0, v1, 0x20), \
                       _mm256_permute2f128_ps(v0, v1, 0x31))

如果您只使用一个输入向量,那么您可以稍微简化一下。

【讨论】:

  • 感谢您的回答。我只使用一个向量 - 这将如何简化?延迟低吗?
  • 您可能希望在您同时做的任何其他事情的背景下简化它(在这种情况下,大概只是一个水平减少总和)。上述实现是原生 _mm256_hadd_ps 的通用替代品,其行为与您对完整 256 位 SIMD 实现的预期一样(而不是在涉及水平操作时使用 AVX 获得的 2x128 位 SIMD 组合)。它已经过测试,我建议现在“按原样”使用它,然后仅在需要时考虑对其进行简化/优化。
猜你喜欢
  • 2012-12-02
  • 2021-06-26
  • 2012-05-14
  • 2018-12-18
  • 2020-03-08
  • 2020-09-04
  • 2011-12-14
  • 2013-04-08
  • 2013-04-09
相关资源
最近更新 更多