【问题标题】:Using OpenMP stops GCC auto vectorising使用 OpenMP 停止 GCC 自动矢量化
【发布时间】:2013-01-29 11:09:48
【问题描述】:

我一直在努力使我的代码能够被 GCC 自动矢量化,但是,当我包含 -fopenmp 标志时,它似乎停止了所有自动矢量化的尝试。我正在使用ftree-vectorize -ftree-vectorizer-verbose=5 对其进行矢量化和监控。

如果我不包含该标志,它会开始给我很多关于每个循环的信息,如果它是矢量化的,为什么不。当我尝试使用 omp_get_wtime() 函数时,编译器停止,因为它无法链接。一旦包含标志,它就会简单地列出每个函数并告诉我它在其中矢量化了 0 个循环。

我已经阅读了其他几个地方提到了这个问题,但他们并没有真正找到任何解决方案:http://software.intel.com/en-us/forums/topic/295858http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46032。 OpenMP 是否有自己的处理向量化的方式?我需要明确告诉它吗?

【问题讨论】:

  • 我认为您可以在answer 中找到有关此问题的合理信息。
  • 谢谢,它描述了如何将 SIMD 与 OpenMP 一起使用,但它似乎无法解释为什么当我使用 OpenMP 时,已经工作的 SIMD 实现停止工作。有没有办法两者都用?
  • 这也意味着我只能对相同数量的位进行操作,它们只是在数字之间进行分割。在使用 GCC 进行操作时,我没有被问到我想将多少个拆分到一个寄存器上。由于我使用的是大学的“超级计算机”,因此我假设硬件为 SIMD 提供了额外的空间。我怎么知道这是否正确?
  • 硬件为 AMD 处理器,将使用 3Dnow!
  • 最终我的问题是,由于硬件确实有特定的寄存器可以容纳更多来帮助矢量化,考虑到该链接中给出的函数说它将分裂正常,我如何使用 GCC 执行此操作大小寄存器成块。

标签: c gcc openmp vectorization


【解决方案1】:

我将尝试简要回答您的问题。

  1. OpenMP 有自己处理矢量化的方法吗?

是的...但从传入的 OpenMP 4.0 开始。上面发布的link 提供了有关此构造的很好的见解。另一方面,当前的 OpenMP 3.1 并没有“意识到” SIMD 概念。因此,在实践中(或者至少在我的经验中)发生的情况是,每当在循环中使用 openmp 工作共享结构时,自动矢量化机制就会被禁止。无论如何,这两个概念是正交的,您仍然可以从两者中受益(参见另一个 answer)。

  1. 我需要明确告诉它吗?

恐怕是的,至少目前是这样。我将开始以明确矢量化的方式重写正在考虑的循环(即,我将在 Intel 平台上使用内在函数,在 IBM 上使用 Altivec 等等)。

【讨论】:

  • 非常感谢。您的第一个链接给出了函数VECTOR_ADD。我读过它使用一个正常大小的寄存器来执行此操作,因此只允许对少量数字进行矢量化。我知道我的硬件有特定的寄存器来处理 SIMD,这样就不会发生这种情况。有没有办法让 OpenMP 使用这个寄存器?考虑到在 GCC 为我完成这一切之前,我是否需要使用这些功能?我不明白为什么 OpenMP 会停止此表单的工作。您的第二个链接说他们可以一起工作,但不是我将如何实现这一点。再次非常感谢您。
  • 主要思想是 OpenMP 必须不知道 SIMDization,因为您在 VECTOR_ADD 中处理它。我从未使用过 3Dnow,但在 Intel 平台上,您可以使用 intrinsics 显式矢量化代码。主要缺点是要么失去可移植性(因为内在函数在其他平台上不起作用),要么失去可读性/可维护性(因为条件编译)。
  • 对于这个项目,可维护性和可移植性并不重要。我目前没有使用 VECTOR_ADD,我只是把它放在一个循环中,让 GCC 可以看到正在发生的事情并自动对其进行矢量化。
【解决方案2】:

您在问“为什么在启用 OpenMP 时 GCC 不能进行矢量化?”。

看来这可能是 GCC 的错误 :) http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46032

否则,OpenMP API 可能会引入阻止自动矢量化的依赖项(控制或数据)。要自动验证,给定的代码必须不依赖数据/控制。使用 OpenMP 可能会导致一些虚假的依赖关系。

注意:OpenMP(4.0 之前)是使用线程级并行性,这与 SIMD/矢量化正交。一个程序可以同时使用 OpenMP 和 SIMD 并行。

【讨论】:

    【解决方案3】:

    GCC 矢量化器中的一个缺点似乎已在最近的 GCC 版本中得到解决。在我的测试案例中,GCC 4.7.2 成功矢量化了以下简单循环:

    #pragma omp parallel for schedule(static)
    for (int i = 0; i < N; i++)
       a[i] = b[i] + c[i] * d;
    

    同时 GCC 4.6.1 没有并且它抱怨循环包含无法分析的函数调用或数据引用。矢量化器中的错误是由 GCC 实现parallel for 循环的方式触发的。当 OpenMP 结构被处理和扩展时,简单的循环代码被转换成类似这样的东西:

    struct omp_fn_0_s
    {
        int N;
        double *a;
        double *b;
        double *c;
        double d;
    };
    
    void omp_fn_0(struct omp_fn_0_s *data)
    {
        int start, end;
        int nthreads = omp_get_num_threads();
        int threadid = omp_get_thread_num();
    
        // This is just to illustrate the case - GCC uses a bit different formulas
        start = (data->N * threadid) / nthreads;
        end = (data->N * (threadid+1)) / nthreads;
    
        for (int i = start; i < end; i++)
           data->a[i] = data->b[i] + data->c[i] * data->d;
    }
    
    ...
    
    struct omp_fn_0_s omp_data_o;
    
    omp_data_o.N = N;
    omp_data_o.a = a;
    omp_data_o.b = b;
    omp_data_o.c = c;
    omp_data_o.d = d;
    
    GOMP_parallel_start(omp_fn_0, &omp_data_o, 0);
    omp_fn_0(&omp_data_o);
    GOMP_parallel_end();
    
    N = omp_data_o.N;
    a = omp_data_o.a;
    b = omp_data_o.b;
    c = omp_data_o.c;
    d = omp_data_o.d;
    

    4.7 之前的 GCC 中的矢量化器无法矢量化该循环。这不是 OpenMP 特有的问题。无需 OpenMP 代码即可轻松重现它。为了确认这一点,我编写了以下简单的测试:

    struct fun_s
    {
       double *restrict a;
       double *restrict b;
       double *restrict c;
       double d;
       int n;
    };
    
    void fun1(double *restrict a,
              double *restrict b,
              double *restrict c,
              double d,
              int n)
    {
       int i;
       for (i = 0; i < n; i++)
          a[i] = b[i] + c[i] * d;
    }
    
    void fun2(struct fun_s *par)
    {
       int i;
       for (i = 0; i < par->n; i++)
          par->a[i] = par->b[i] + par->c[i] * par->d;
    }
    

    由于restrict 用于指定不会发生别名的关键字,人们会期望这两个代码(注意 - 这里没有 OpenMP!)应该同样好地矢量化。不幸的是,GCC fun1 中的循环向量化,但未能将fun2 中的循环向量化,原因与编译 OpenMP 代码时相同。

    原因是矢量化器无法证明par-&gt;d 不在par-&gt;apar-&gt;bpar-&gt;c 指向的内存中。 fun1 并非总是如此,可能有两种情况:

    • d 在寄存器中作为值参数传递;
    • d 在堆栈上作为值参数传递。

    在 x64 系统上,System V ABI 要求前几个浮点参数在 XMM 寄存器中传递(启用 AVX 的 CPU 上的 YMM)。这就是d 在这种情况下被传递的方式,因此没有指针可以指向它——循环被向量化了。在 x86 系统上,ABI 要求将参数传递到堆栈上,因此 d 可能被三个指针中的任何一个别名。实际上,如果使用 -m32 选项指示生成 32 位 x86 代码,GCC 拒绝向量化 fun1 中的循环。

    GCC 4.7 通过插入运行时检查来解决这个问题,确保dpar-&gt;d 都没有别名。

    摆脱d 删除了不可证明的非混叠,以下 OpenMP 代码由 GCC 4.6.1 向量化:

    #pragma omp parallel for schedule(static)
    for (int i = 0; i < N; i++)
       a[i] = b[i] + c[i];
    

    【讨论】:

    • 很好的答案。但是您能否多说一下“这只是为了说明情况- GCC 使用了一些不同的公式”。 GCC 使用什么公式?
    • @Zboson,我可以把它贴在这里(丑陋),但你宁愿运行gcc -fdump-tree-all -fopenmp foo.c 并在 OpenMP 扩展后自己检查 AST,通常位于 foo.c.015t.ompexp。不同之处在于 GCC 通过对第一个 r 线程进行一次额外迭代来分配除法 r = N % num_threads 的余数。
    【解决方案4】:

    我在搜索有关 gcc 4.9 选项 openmp-simd 的 cmets 时遇到了这篇文章,该选项应该激活 OpenMP 4 #pragma omp simd 而无需激活 omp parallel(线程)。 gcc bugzilla pr60117(已确认)显示了 pragma omp 阻止在没有 pragma 的情况下发生的自动矢量化的情况。

    gcc 不会向量化 omp 并行 for 即使使用 simd 子句(并行区域只能自动向量化嵌套在并行 for 下的内部循环)。我不知道除了 icc 14.0.2 之外的任何编译器,可以推荐用于 simd 的 #pragma omp parallel 的实现;对于其他编译器,需要 SSE 内在函数编码才能获得此效果。

    在我的测试中,Microsoft 编译器没有在并行区域内执行任何自动矢量化,这表明 gcc 在这种情况下具有明显的优势。

    将单个循环的并行化和矢量化结合起来有几个困难,即使是最好的实现也是如此。通过将矢量化添加到并行循环中,我很少看到超过 2 倍或 3 倍的加速。例如,使用 AVX 双数据类型的向量化,有效地将块大小减少了 4 倍。典型的实现只能在整个数组对齐的情况下实现对齐的数据块,并且块也是向量宽度的精确倍数.当块没有全部对齐时,由于对齐方式的变化,存在固有的工作不平衡。

    【讨论】:

      猜你喜欢
      • 2018-12-16
      • 1970-01-01
      • 1970-01-01
      • 2013-03-06
      • 2021-08-10
      • 2016-01-16
      • 2019-04-05
      • 2015-07-30
      相关资源
      最近更新 更多