【问题标题】:Is it safe to use openMP simd for following loops?使用 openMP simd 进行以下循环是否安全?
【发布时间】:2018-09-16 19:10:49
【问题描述】:

我只是更仔细地研究 OpenMP simd 构造,并且有三个循环似乎没有被 gcc 向量化(简要性能测试),但我认为它们可以。所以我想知道,添加 simd pragma 是否安全以及为什么 gcc 不对其进行矢量化。

首先是矩阵乘法,其值存储为单个数组:

#pragma omp parallel for
    for(size_t row = 0; row < 100; ++row){
    {#pragma omp simd}
        for(size_t col = 0; col < 100; ++col){              
            float sum = c[row * 100 + col];
            for(size_t k = 0; k < 100; k++){
                sum += a[rows * 100 + k] * b[k * 100 + col];
            }
            c[row * 100 + col] = sum;
        }

我知道 b 没有转置,这会影响性能。通过添加 simd pragma,代码变得更快。由于内循环,自动矢量化是不可能的吗?

对于第二个示例,我尝试了 OpenMP 的自定义归约声明功能,但实际上并不需要。

#pragma omp declare reduction(sum : double : omp_out += omp_in) initializer(omp_priv = omp_orig)
double red_result = 0;
#pragma omp parallel for {simd} reduction(sum:red_result)
    for(size_t i = 0; i < 100; ++i){            
        red_result = red_result + a[i];
    }

减少会阻止矢量化吗?因为我认为它应该可以正常工作?

最后一个例子是一个复杂的循环,有另一个内部循环和函数调用。简化后看起来像这样:

#pragma omp parallel for {simd}
for(size_t i = 0; i < 100; ++i){
  [..]
  for(size_t j = 0; j < 100; j++){
    if(j != i){
      float k2 = a[i] - b[j];
       k = std::sqrt(k2);           
    }
  }
  [do more with k]
}

所以这里的问题可能是 sqrt 调用,它不能被矢量化?但是使用 simd pragma 的性能应该更好吗?一些简短的测试表明情况确实如此,但是如果由于 std::sqrt 而无法进行自动矢量化,那么为什么要用 pragma 来实现呢?

感谢您的帮助! :)

【问题讨论】:

  • FP 数学不是关联的。如果没有 -ffast-math 或允许它们以不同顺序求和的 OpenMP pragma,编译器无法自动矢量化 FP 缩减。
  • x86 具有对 SIMD sqrt 的硬件支持。 sqrtpd 在大多数 CPU 上的吞吐量与 sqrtsd 一样好,但并行处理 2 个 double 平方根。 agner.org/optimize.
  • 过去,gcc 在 omp 并行 simd 的情况下会忽略 simd,因此可以合理地说并行禁用矢量化(至少在需要 simd 的地方)。上面的帖子暗示这在 gcc 7.1 中发生了变化。即使使用 icc,我的经验是需要显式嵌套循环来完成并行 simd。

标签: c++ openmp vectorization simd


【解决方案1】:

对于math.h 中的数学函数,您的编译器需要实现数学函数的向量化版本。 GCC 使用libmvec 执行此操作,而 ICC 使用SVML 执行此操作。据我所知,Clang 没有对矢量化数学函数的原生支持。

让我们考虑以下代码:

void foo(float * __restrict a, float * __restrict b) {    
    a = (float*)__builtin_assume_aligned(a, 16);
    b = (float*)__builtin_assume_aligned(b, 16);          
    for(int i = 0; i < 100; ++i) {
        b[i] = sqrtf(a[i]);
    }
}

void foo2(float * __restrict a, float * __restrict b) {    
    a = (float*)__builtin_assume_aligned(a, 16);
    b = (float*)__builtin_assume_aligned(b, 16);          
    for(int i = 0; i < 100; ++i) {
        b[i] = sinf(a[i]);
    }
}

GCC、ICC 和 Clang 向量化 sqrtf(使用牛顿方法的一次迭代)。 GCC 和 ICC 分别使用 libmvec (_ZGVbN4v_sinf) 和 SVML (__svml_sinf4) 向量化 sinf。 Clang 不会向量化 sinf。见godboltsqrt 是一种特殊情况(因为 x86 指令集已矢量化 sqrt 指令),它可以在没有矢量化数学库的情况下内联。

【讨论】:

  • 因为你没有声明 float *__restrct c 你需要 omp simd 来断言没有别名。如果您设置了适当的选项,这应该使用 simd sqrt(无牛顿步骤)进行矢量化。在这种简单的情况下,编译器使用内联代码来提高效率
  • @tim18,谢谢,这是一个更好的解决方案。现在 GCC、ICC 和 Clang 都在没有 OpenMP godbolt.org/g/Lsznwh 的情况下对其进行了矢量化。我认为sqrt 是一个特例。如果使用 sin,则需要 libmvec 或 SVML。
  • 是的,当然 sqrt 是特殊的,因为它可以使用内置的 sqrt 或牛顿迭代。后者在 svml 中没有意义
  • 这个案例有点荒谬。您是否在测试编译器是否知道通过丢弃 99 个结果来消除内部循环?
  • @tim18,我只是想回答 OP 问题中的第三个问题,因为这对我来说是最有趣的。我不是要消除内循环。我认为sqrt 可能是 OP 的一个错误选择,因为它有一个特殊的解决方案。我认为 OP 希望对 math.h 中的函数有一个通用的答案。
猜你喜欢
  • 2016-03-20
  • 2019-10-15
  • 1970-01-01
  • 2011-04-16
  • 2016-10-19
  • 2016-08-18
  • 1970-01-01
  • 1970-01-01
  • 2012-10-11
相关资源
最近更新 更多