GCC对矢量化的暗示

GCC对矢量化的暗示,gcc,vectorization,auto-vectorization,Gcc,Vectorization,Auto Vectorization,我希望GCC对下面的代码进行矢量化。-fopt info告诉我GCC当前不可用。我认为问题在于W的快速访问,或者k的向后递增。请注意,height和width是常量,index\u type当前设置为无符号长 我删除了一些评论 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;

我希望GCC对下面的代码进行矢量化。
-fopt info
告诉我GCC当前不可用。我认为问题在于
W
的快速访问,或者
k
的向后递增。请注意,
height
width
是常量,
index\u type
当前设置为
无符号长

我删除了一些评论

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  }
编辑:

最小的例子是

我正试着用布拉斯。在最简单的示例中,它运行得更快,但在整个代码中它运行得更慢。。。不知道为什么

编辑:

编译器正在优化代码。固定的。布拉斯现在更快了。修复是在整个代码上进行的,而不是最小的示例

编辑:

与上一次编辑中的链接中的代码相同

#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;
}
#包括
#包括
#包括
#包括
类型定义浮点值\u类型;
typedef无符号长索引类型;
静态值类型F(值类型v){
返回1.0/(1.0+exp(-v));
}
静态值类型DF(值类型v){
常数值\类型Ev=exp(-v);
返回Ev/((1.0+Ev)*(1.0+Ev));
}
#ifndef与_BLAS
静态void get\u Yp(常量值类型*\uuuu限制\uuuuu Ap,常量值类型*\uuu限制\uuuu W,
值类型*\uuuuuuuuuuuuuuuuuuuuyp,常数值类型*\uuuuuuuuuuuuuuuuuudP,
常数索引类型高度,常数索引类型宽度){
对于(索引类型i=0;i0;k--){

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

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

  • j-loop可很好地向量化具有恒定“宽度”元素步长的SIMD简化循环。您可以使用现代编译器对其进行向量化。此代码可使用英特尔编译器进行向量化,在某些情况下,GCC应可对其进行向量化

  • 首先,归约是“可向量化”的真循环携带依赖的特例。因此,除非“归约”模式(a)由编译器自动识别(不是那么容易,严格来说不是那么有效/预期的行为)或(b),否则无法安全地将其向量化开发人员使用OpenMP或类似标准明确地与编译器沟通

  • 若要向编译器“传达”存在缩减,您需要使用
    #pragma omp simd reduction(+:变量名称)
    在j-loop之前

    这仅从OpenMP4.0开始受支持。因此您必须使用支持OpenMP4.x的GCC版本。引自:“GCC 4.9支持C/C++的OpenMP 4.0,GCC 4.9.1也支持Fortran”

    我还将临时使用局部变量来累积还原(OpenMP4.0要求以这种方式使用还原变量):

    tmpSUM=0;
    #pragma omp simd简化(+:tmpSUM)
    
    对于(索引类型j=0;j1),我在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];
    }
    
    我不确定为什么只有ICC矢量化了
    foo
    。我似乎很清楚,没有依赖性

    GCC不展开循环(和
    -funroll循环
    )。MSVC展开两次,铿锵展开四次,ICC展开八次。由于至少et 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]);
    
    value\u类型和=0.0;
    
    对于(索引类型j=0;jTry创建一个最小的、完整的和可验证的示例来重现问题。我不确定是正确的标记。尝试在缩减中使用
    sum
    而不是
    Yp[k*width+I]
    ,然后在缩减后执行
    Yp[k*width+I]=sum
    。但你真的应该提供一个最小的例子,正如@RossRidge所说。另外,请记住,GCC无论如何都不会随着减少而展开。ICC展开两次,发出四次叮当声。在许多英特尔处理器中,四次应该是好的(因为Haswell,你可能需要展开四次以上,但要做到这一点就更难了)所以对于归约的自动矢量化来说,Clang是目前最好的。你是UCSD粒子物理专业的学生。我对那里的一些粒子物理专业很熟悉(至少我已经和他们谈过几次).你打算提供一个最简单的工作示例吗?这个最简单的示例昨天被编辑到了帖子中。你看不到它吗?我放了一个链接,而不是放所有的代码。也许我应该把代码放在问题中。你知道谁吗?我在实验CMS小组的Frank手下工作。谢谢。你能给我进一步的信息吗打开?当然,这取决于您需要知道什么。原则上,使用OpenBLAS很容易,但也有一些潜在的陷阱(特别是如果您打算将其用于集群上的计算)。主要信息来源是OpenBLAS的GitHub页面。除了MKL,OpenBLAS可能是BLAS接口最快、开发最完善的实现,但与MKL相比,它是免费提供的。因此,有相当多的社区可以帮助解决问题……我喜欢GCC用于自动矢量化,当我
    void foo(float *x) 
        unsigned i;
        for (i=0; i<1024; i++) x[0] += x[1024 + i];
    }
    
    void foo2(float *x) {
        float sum = 0.0f;
        unsigned i;
        for (i=0; i<1024; i++) sum += x[1024 + i];
        x[0] = sum;
    }
    
    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]);