【问题标题】:GCC Hinting at VectorizationGCC 暗示向量化
【发布时间】:2016-04-23 23:44:50
【问题描述】:

我希望 GCC 对以下代码进行矢量化处理。-fopt-info 告诉我 GCC 目前还没有。我认为问题在于W 的跨步访问或k 的向后递增。注意heightwidth 是常量,index_type 当前设置为unsigned long

我删除了一些 cmets

114  for (index_type k=height-1;k+1>0;k--) {
116    for (index_type i=0;i<width;i++) {
117      Yp[k*width + i] = 0.0;                                                            
119      for (index_type j=0;j<width;j++) {                                                                            
121        Yp[k*width + i] += W[k*width*width + j*width + i]*Yp[(k+1)*width + j];
122      }
123      Yp[k*width + i] *= DF(Ap[k*width + i]);
124    }
125  }

我正在使用gcc -O3 -ffast-math -fopt-info -std=c11 ./neural.c -o neural -lm进行编译

有没有一种好方法可以使这个矢量化?您可以向我介绍更多信息吗?

我的索引方法是不是一个坏主意(即k*width*width + ...)?我需要动态分配,我认为将事物保持在内存中会更好地进行优化。

编辑:这可能有用

这些行的-fopt-info-missed 输出

./neural.c:114:3: note: not vectorized: multiple nested loops.
./neural.c:114:3: note: bad loop form.
./neural.c:116:5: note: not vectorized: control flow in loop.
./neural.c:116:5: note: bad loop form.
./neural.c:119:7: note: step unknown.
./neural.c:119:7: note: reduction used in loop.
./neural.c:119:7: note: Unknown def-use cycle pattern.
./neural.c:119:7: note: not vectorized: complicated access pattern.
./neural.c:119:7: note: bad data access.
./neural.c:110:21: note: not vectorized: not enough data-refs in basic block.
./neural.c:110:58: note: not vectorized: not enough data-refs in basic block.
./neural.c:110:62: note: not vectorized: not enough data-refs in basic block.
./neural.c:117:18: note: not vectorized: not enough data-refs in basic block.
./neural.c:114:37: note: not vectorized: not enough data-refs in basic block.

编辑:

最小的例子是HERE

我正在尝试使用 BLAS。在最小的例子中,它运行得更快,但在整个代码上它更慢......不知道为什么

编辑:

编译器正在优化代码。固定的。 BLAS 现在更快了。修复是针对整个代码,而不是最小的示例。

编辑:

与之前编辑的链接中的代码相同

#include <math.h>
#include <cblas.h>
#include <stdlib.h>
#include <stdio.h>

typedef float value_type;
typedef unsigned long index_type;

static value_type F(value_type v) {
  return 1.0/(1.0 + exp(-v));
}

static value_type DF(value_type v) {
  const value_type Ev = exp(-v);
  return Ev/((1.0 + Ev)*(1.0 + Ev));
}

#ifndef WITH_BLAS

static void get_Yp(const value_type * __restrict__ Ap, const value_type * __restrict__ W,
           value_type * __restrict__ Yp, const value_type * __restrict__ Dp,
           const index_type height, const index_type width) {
  for (index_type i=0;i<width;i++) {
    Yp[height*width + i] = 2*DF(Ap[height*width + i])*(Dp[i] - F(Ap[height*width + i]));
  }

  for (index_type k=height-1;k+1>0;k--) {
    for (index_type i=0;i<width;i++) {
      Yp[k*width + i] = 0.0;
      for (index_type j=0;j<width;j++) {
    Yp[k*width + i] += W[k*width*width + j*width + i]*Yp[(k+1)*width + j];
      }
      Yp[k*width + i] *= DF(Ap[k*width + i]);
    }
  }
}

#else

static void get_Yp(const value_type * __restrict__ Ap, const value_type * __restrict__ W,
           value_type * __restrict__ Yp, const value_type * __restrict__ Dp,
           const index_type height, const index_type width) {
  for (index_type i=0;i<width;i++) {
    Yp[height*width + i] = 2*DF(Ap[height*width + i])*(Dp[i] - F(Ap[height*width + i]));
  }

  for (index_type k=height-1;k+1>0;k--) {
    cblas_sgemv(CblasRowMajor, CblasTrans, width, width, 1,
        W+k*width*width, width, Yp+(k+1)*width, 1, 0, Yp+k*width, 1);
    for (index_type i=0;i<width;i++)
      Yp[k*width + i] *= DF(Ap[k*width + i]);
  }
}

#endif

int main() {
  const index_type height=10, width=10000;

  value_type *Ap=malloc((height+1)*width*sizeof(value_type)),
    *W=malloc(height*width*width*sizeof(value_type)),
    *Yp=malloc((height+1)*width*sizeof(value_type)),
    *Dp=malloc(width*sizeof(value_type));

  get_Yp(Ap, W, Yp, Dp, height, width);
  printf("Done %f\n", Yp[3]);

  return 0;
}

【问题讨论】:

  • 尝试创建一个最小的、完整的和可验证的示例来重现问题。
  • 我不确定vectorization 是不是正确的标签。
  • 尝试在归约中使用sum 而不是Yp[k*width + i],然后在归约后使用Yp[k*width + i] = sum。但是你真的应该像@RossRidge 所说的那样提供一个最小的例子。另外,请记住,GCC 无论如何都不会展开减少。 ICC 展开两次,Clang 展开四次。在许多英特尔处理器上四倍应该是好的(因为 Haswell 您可能需要展开四次以上,但实现这一点更困难)因此对于减少的自动矢量化 Clang 是目前最好的。
  • 你是加州大学圣地亚哥分校粒子物理学专业的学生。我认识那里的一些粒子物理学教授(至少我和他们谈过几次)。您要提供一个最小的工作示例吗?
  • 昨天在帖子中编辑了最小示例。你不能得到它吗?我放了一个链接而不是放所有代码。也许我应该将代码放入问题中。你认识谁?我在实验性 CMS 小组的 Frank 手下工作。

标签: gcc vectorization auto-vectorization


【解决方案1】:
  1. j-loop 可很好地矢量化 SIMD 缩减循环,具有恒定的“宽度”元素步幅。您可以使用现代编译器对其进行矢量化。此代码可使用英特尔编译器进行矢量化,并且在某些情况下应可通过 GCC 进行矢量化。

  2. 首先,Reduction 是“可向量化”true 循环携带依赖的特例。所以你不能安全地对其进行矢量化,除非“减少”模式是(a)编译器自动识别的(不是那么容易,严格来说不是那么有效/预期的行为)或(b)开发人员使用 OpenMP 或类似方式明确地与编译器通信标准。

要向编译器“传达”减少的信息 - 您需要使用 #pragma omp simd reduction (+ : variable_name) 在 j 循环之前。

这仅从 OpenMP4.0 开始受支持。所以你必须使用支持 OpenMP4.x 的 GCC 版本。来自https://gcc.gnu.org/wiki/openmp 的引用:“GCC 4.9 支持 C/C++ 的 OpenMP 4.0,也支持 Fortran 的 GCC 4.9.1”

我也会使用临时局部变量来累积缩减(OpenMP4.0 需要使用缩减变量):

 tmpSUM = 0; 
 #pragma omp simd reduction (+: tmpSUM) 
 for (index_type j=0;j<width;j++) {                                                                            
        tmpSUM += W[k*width*width + j*width + i]*Yp[(k+1)*width + j];
      }
 Yp[k*width + i] = tmpSUM
  1. 我还建议使用带符号的 int 而不是无符号的,因为无符号的归纳变量对所有现代矢量化器都非常不利,至少会引入额外的开销。如果使用 unsigned 是“混淆”GCC 的主要原因之一,我不会感到惊讶。

  2. 现在,您可能对我的回复不满意,因为它说明了它应该如何工作(以及它在 ICC/ICPC 中如何工作)。正如在 GCC 优化报告中所见,它没有考虑 GCC 的具体细微差别(这对于减少似乎起到了相反的作用)。

所以,如果你仍然局限于 GCC,我建议:

  • 确保它是足够新鲜的 GCC(4.9 或更高版本)
  • 使用带符号的归纳变量并仍然尝试在临时本地 tmp SUM 上减少 omp simd(因为它应该启用更高级 无论如何矢量化技术)
  • 如果以上都没有帮助,请查看此处描述的“奇怪”事物(与您的情况非常相似): What do gcc's auto-vectorization messages mean? 或考虑使用其他编译器/编译器版本。

    1. 最后一条评论:您的代码中的访问模式和更普遍的 const-stride 是否如此糟糕? 回答:对于某些平台/编译器,const-stride 不会 扼杀你的性能。但是,理想情况下,您需要更多缓存友好的算法。检查Not sure how to explain some of the performance results of my parallelized matrix multiplication code。如果您的代码确实受内存限制并且您没有时间自己处理内存优化,则另一种选择是考虑 MKL 或 BLAS(如其他回复所建议的)。

【讨论】:

  • 如果 OP 只是提供了一个工作示例,我相信我可以让 GCC 在不使用 omp simd 的情况下对其进行矢量化。当然,在我看到代码之前我不能肯定地说,但目前对我来说看起来并不难。
  • @Z 玻色子。我同意你的看法,这也是我的期望;你对 GCC 的经验应该是我的 100 倍。在我的回复中,我想专注于“形式”/广义的答案,特别是考虑到减少(在给定情况下已证明依赖)对于自动矢量化来说是正式非法的(取决于您遵循的编程模型哲学;老 Cray 家伙会不同意)。
  • OP 使用了-ffast-math,它允许减少浮点数。不幸的是,GCC 不会展开循环。但omp simd 也没有。如果 OpenMP simd 已展开,它将很有用。
  • 不要将防止矢量化的 fp 模型与已证明的依赖性混合在一起,从而正式阻止减少的自动矢量化,而不管 fp 模型。在 intel 编译器中,您可以结合 unroll 和 omp simd pragma,尽管 OMP4.x 确实缺乏许多重要的 simd 编程模型功能
  • 我知道你的意思。我还没有尝试过代码。你看到明显的依赖了吗?
【解决方案2】:

我在 GCC 5.3.1、Clang 3.7.1、ICC 13.0.1 和 MSVC 2015 中测试了以下代码

void foo(float *x) 
    unsigned i;
    for (i=0; i<1024; i++) x[0] += x[1024 + i];
}

我在 GCC、Clang 和 ICC 中使用了 -Ofast,在 MSVC 中使用了 /O2 /fp:fast。查看程序集表明只有 ICC 设法对循环进行矢量化。

但是,使用相同的编译选项,所有编译器都矢量化了以下代码

void foo2(float *x) {
    float sum = 0.0f;
    unsigned i;
    for (i=0; i<1024; i++) sum += x[1024 + i];
    x[0] = sum;
}

我不确定为什么只有 ICC 矢量化 foo。我似乎很清楚没有依赖关系。

GCC 不会展开循环(和-funroll-loopsdoes not help)。 MSVC 展开两次,Clang 展开四次,ICC 八次。英特尔处理器,因为至少 Core2 有至少 3 个周期的加法延迟,因此展开四次或更多次将比两次或根本没有更好地工作。

无论如何使用

value_type sum = 0.0;
for (index_type j=0;j<width;j++) {
    sum += W[k*width*width + j*width + i]*Yp[(k+1)*width + j];
}
Yp[k*width + i] = sum*DF(Ap[k*width + i]);

矢量化你的循环。

【讨论】:

    【解决方案3】:

    根据我的经验,要求 GCC 正确矢量化是很麻烦的。特别是如果您希望充分利用现代硬件(例如 AVX2)。我在我的代码中处理了很多向量化。我的解决方案:根本不要尝试使用 GCC 进行矢量化。而是根据 BLAS(基本线性代数子程序)调用来制定您希望向量化的所有操作,并与为现代硬件实现 BLAS 的 OpenBLAS 库链接。 OpenBLAS 还提供共享内存节点上的并行化(使用 pthread 或 OpenMP),根据您的应用程序,这对于进一步提高执行速度非常重要:通过这种方式,您可以将矢量化与(共享内存)并行化结合起来。

    此外,我建议您调整内存以充分利用 AVX 和/或 AVX2 等。 IE。不要使用 malloc 或 new 分配内存,使用 memalign 或 aligned_alloc(取决于您的系统支持的内容)。例如,如果您打算使用 AVX2,您应该调整分配,使地址是 64 字节的倍数(8 * 8 双倍)。

    【讨论】:

    • 谢谢。您能向我介绍更多信息吗?
    • 当然,这取决于您需要了解的内容。原则上,使用 OpenBLAS 很容易,但存在一些潜在的缺陷(尤其是如果您打算将其用于集群上的计算)。主要信息来源是 OpenBLAS 的 GitHub 页面。除了 MKL,OpenBLAS 可能是 BLAS 接口的最快和开发最好的实现,但与 MKL 相比,它是免费提供的。所以有相当多的社区可以帮助解决问题......
    • 我喜欢 GCC 进行自动矢量化,当我不喜欢它时,我使用内在函数(好吧,无论如何我大部分时间都使用内在函数进行 SIMD)所以我不是你的答案的忠实粉丝.
    • @Zboson:我没有说你不能做不同的事情。但是你必须明白:不是每个人(尤其是在科学应用中)都有时间深入研究编译器的工作原理(这里是在矢量化的上下文中)以获得令人满意的结果——尤其是如果你必须处理一打不涉及计算机或编程的项目的其他方面,以及是否必须定期生成大量可移植代码。这是一种无需大量投资即可充分利用机器(可能还有未来的机器!)的解决方案。
    • 顺便说一句,我没有对你投反对票。主要是因为这是我很少做的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-09
    • 2015-09-07
    • 2018-05-08
    • 2017-05-29
    • 1970-01-01
    • 2018-11-09
    • 2020-12-22
    相关资源
    最近更新 更多