c - 通过 SSE/AVX 使用 OpenMP 减少

标签 c openmp sse avx

我想使用 OpenMP 和 SIMD 对数组进行归约。我读到 OpenMP 的减少相当于:

inline float sum_scalar_openmp2(const float a[], const size_t N) {
    float sum = 0.0f;
    #pragma omp parallel
    {
        float sum_private = 0.0f;
        #pragma omp parallel for nowait
        for(int i=0; i<N; i++) {
            sum_private += a[i];
        }
        #pragma omp atomic
        sum += sum_private;
    }
    return sum;
}

我从以下链接得到了这个想法: http://bisqwit.iki.fi/story/howto/openmp/#ReductionClause 但是原子也不支持复杂的运算符。我所做的是将 atomic 替换为 critical 并使用 OpenMP 和 SSE 实现缩减,如下所示:

#define ROUND_DOWN(x, s) ((x) & ~((s)-1))
inline float sum_vector4_openmp(const float a[], const size_t N) {
    __m128 sum4 = _mm_set1_ps(0.0f);
    #pragma omp parallel 
    {
        __m128 sum4_private = _mm_set1_ps(0.0f);
        #pragma omp for nowait
        for(int i=0; i < ROUND_DOWN(N, 4); i+=4) {
            __m128 a4 = _mm_load_ps(a + i);
            sum4_private = _mm_add_ps(a4, sum4_private);
        }
        #pragma omp critical
        sum4 = _mm_add_ps(sum4_private, sum4);
    }
    __m128 t1 = _mm_hadd_ps(sum4,sum4);
    __m128 t2 = _mm_hadd_ps(t1,t1);
    float sum = _mm_cvtss_f32(t2);  
    for(int i = ROUND_DOWN(N, 4); i < N; i++) {
        sum += a[i];
    }
    return sum;
} 

然而,这个功能并没有我希望的那么好。我正在使用 Visual Studio 2012 Express。我知道我可以通过多次展开 SSE 加载/添加来提高性能,但这仍然比我预期的要少。

通过运行等于线程数的数组切片,我获得了更好的性能:

inline float sum_slice(const float a[], const size_t N) {
    int nthreads = 4;
    const int offset = ROUND_DOWN(N/nthreads, nthreads);
    float suma[8] = {0};
    #pragma omp parallel for num_threads(nthreads) 
    for(int i=0; i<nthreads; i++) {
        suma[i] = sum_vector4(&a[i*offset], offset);
    }
    float sum = 0.0f;
    for(int i=0; i<nthreads; i++) {
        sum += suma[i]; 
    }
    for(int i=nthreads*offset; i < N; i++) {
        sum += a[i];
    }
    return sum;    
}

inline float sum_vector4(const float a[], const size_t N) {
    __m128 sum4 = _mm_set1_ps(0.0f);
    int i = 0;
    for(; i < ROUND_DOWN(N, 4); i+=4) {
        __m128 a4 = _mm_load_ps(a + i);
        sum4 = _mm_add_ps(sum4, a4);
    }
    __m128 t1 = _mm_hadd_ps(sum4,sum4);
    __m128 t2 = _mm_hadd_ps(t1,t1);
    float sum = _mm_cvtss_f32(t2);
    for(; i < N; i++) {
        sum += a[i];
    }
    return sum;

有人知道在 OpenMP 中是否有更好的方法来减少更复杂的运算符吗?

最佳答案

我想您的问题的答案是否定的。我认为没有更好的方法可以在 OpenMP 中使用更复杂的运算符进行缩减。

假设数组是 16 位对齐的,openmp 线程数是 4,人们可能期望 OpenMP + SIMD 的性能增益为 12 到 16 倍。实际上,它可能不会产生足够的性能提升,因为

  1. 创建 openmp 线程会产生开销。
  2. 代码正在为 1 个加载操作执行 1 个加法操作。因此,CPU 没有进行足够的计算。因此,看起来 CPU 大部分时间都花在加载数据上,有点像内存带宽限制。

关于c - 通过 SSE/AVX 使用 OpenMP 减少,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15430069/

相关文章:

c - 我无法将 stren 与指针一起使用

time - 为 Fortran 多线程程序计时

c - 图像的快速转置和 C 中的 Sobel 滤波器优化 (SIMD)

sse - 在整数向量上使用 _mm_shuffle_ps 的含义

十六进制到二进制的转换

c - 在 HTTP 服务器中发出文件结束信号

c - 减少最大值并保存其索引

c++ - OpenMP 代码中止

c - AVX/SSE 版本的 xorshift128+

c - 读入命令行参数以确定多项式