Intrinsics 与 Naive Vector 减少结果的差异

Posted

技术标签:

【中文标题】Intrinsics 与 Naive Vector 减少结果的差异【英文标题】:Discrepancy in result of Intrinsics vs Naive Vector reduction 【发布时间】:2021-12-30 14:13:40 【问题描述】:

我一直在比较 Intrinsics 向量缩减、朴素向量缩减和使用 openmp pragma 的向量缩减的运行时间。但是,我发现在这些情况下结果是不同的。代码如下——(内在向量缩减取自——Fastest way to do horizontal SSE vector sum (or other reduction))

#include <iostream>
#include <chrono>
#include <vector>
#include <numeric>
#include <algorithm>
#include <immintrin.h>


inline float hsum_ps_sse3(__m128 v) 
    __m128 shuf = _mm_movehdup_ps(v);        // broadcast elements 3,1 to 2,0
    __m128 sums = _mm_add_ps(v, shuf);
    shuf        = _mm_movehl_ps(shuf, sums); // high half -> low half
    sums        = _mm_add_ss(sums, shuf);
    return        _mm_cvtss_f32(sums);



float hsum256_ps_avx(__m256 v) 
    __m128 vlow  = _mm256_castps256_ps128(v);
    __m128 vhigh = _mm256_extractf128_ps(v, 1); // high 128
           vlow  = _mm_add_ps(vlow, vhigh);     // add the low 128
    return hsum_ps_sse3(vlow);         // and inline the sse3 version, which is optimal for AVX
    // (no wasted instructions, and all of them are the 4B minimum)


void reduceVector_Naive(std::vector<float> values)
    float result = 0;
    for(int i=0; i<int(1e8); i++)
        result  += values.at(i);
    
    printf("Reduction Naive = %f \n", result);



void reduceVector_openmp(std::vector<float> values)
    float result = 0;
    #pragma omp simd reduction(+: result)
    for(int i=0; i<int(1e8); i++)
        result  += values.at(i);
    

    printf("Reduction OpenMP = %f \n", result);


void reduceVector_intrinsics(std::vector<float> values)
    float result = 0;
    float* data_ptr = values.data();

    for(int i=0; i<1e8; i+=8)
        result  += hsum256_ps_avx(_mm256_loadu_ps(data_ptr + i));
    

    printf("Reduction Intrinsics = %f \n", result);



int main()

    std::vector<float> values;

    for(int i=0; i<1e8; i++)
        values.push_back(1);
    


    reduceVector_Naive(values);
    reduceVector_openmp(values);
    reduceVector_intrinsics(values);

// The result should be 1e8 in each case

但是,我的输出如下-

Reduction Naive = 16777216.000000 
Reduction OpenMP = 16777216.000000 
Reduction Intrinsics = 100000000.000000 

可以看出,只有内函数才能正确计算它,而其他函数则面临精度问题。我完全了解由于四舍五入而使用浮点数可能面临的精度问题,所以我的问题是,为什么内在函数得到正确的答案,即使它实际上也是浮点值算术。

我将其编译为 - g++ -mavx2 -march=native -O3 -fopenmp main.cpp 尝试使用版本7.5.0 以及10.3.0

TIA

【问题讨论】:

SIMD 减少有效地使用了多个累加器(特别是如果你使用多个向量,你应该隐藏 FP 添加延迟),所以它是a step in the direction of pairwise summation。或者至少如果你没有通过减少到循环内的标量来破坏目的!正如我在您链接的 hsum Q&A 中提到的(在关于数组点积的要点中),最后做一次水平的东西; _mm256_add_ps 在循环中。 如果 OpenMP 已正确自动矢量化,您会从中看到 100000000.0。显然,带有边界检查的 .at(i) 而不是 [i],正在击败 OpenMP 自动矢量化。 (如果你使用-ffast-math - godbolt.org/z/EeaWYPsYo,那太天真了)。不过,废话,OpenMP 使用 vgatherqps 而不是连续加载来编写疯狂的代码。 godbolt.org/z/Y8cG6dozo 显示了合理的 OpenMP 矢量化,在循环内索引 const float * 而不是调用 std::vector 的重载 operator[] 并且看不到生成的访问位置是连续的。不幸的是,即使使用 OpenMP,GCC 仍然没有使用多个累加器来隐藏 FP 延迟,将其限制为每 4 个周期 32 个字节。 clang 做得更好,尽管如果它只使用 4 个向量,展开更多似乎是合理的。 godbolt.org/z/ssoqas575 【参考方案1】:

Naïve 循环按 1.0 添加,并在 16777216.000000 停止添加,因为 binary32 浮点数中没有足够的有效数字。

查看此问答:Why does a float variable stop incrementing at 16777216 in C#?

当你添加计算的水平和时,它会加上8.0,所以从它停止添加的数字大约是16777216*8 = 134217728,你只是在你的实验中没有达到它。

【讨论】:

以上是关于Intrinsics 与 Naive Vector 减少结果的差异的主要内容,如果未能解决你的问题,请参考以下文章

Neon Intrinsics各函数介绍

如何将 sklearn.naive_bayes 与(多个)分类特征一起使用? [关闭]

RenderScript Intrinsics 高斯模糊

MapDB ClassNotFoundException:kotlin.jvm.internal.Intrinsics

Naive Bayes理论与实践

RenderScript 自定义 ScriptC 比 Intrinsics 慢