gcc 自动矢量化(未处理的数据参考)

Posted

技术标签:

【中文标题】gcc 自动矢量化(未处理的数据参考)【英文标题】:gcc auto-vectorisation (unhandled data-ref) 【发布时间】:2014-11-20 09:35:31 【问题描述】:

我不明白为什么这样的代码没有用 gcc 4.4.6 向量化

int MyFunc(const float *pfTab, float *pfResult, int iSize, int iIndex)

  for (int i = 0; i < iSize; i++)
     pfResult[i] = pfResult[i] + pfTab[iIndex];


 note: not vectorized: unhandled data-ref

但是,如果我写下面的代码

   int MyFunc(const float *pfTab, float *pfResult, int iSize, int iIndex)

  float fTab =  pfTab[iIndex];
  for (int i = 0; i < iSize; i++)
     pfResult[i] = pfResult[i] + fTab;

gcc 成功自动向量化这个循环

如果我添加 omp 指令

   int MyFunc(const float *pfTab, float *pfResult, int iSize, int iIndex)

  float fTab =  pfTab[iIndex];
  #pragma omp parallel for
  for (int i = 0; i < iSize; i++)
     pfResult[i] = pfResult[i] + fTab;

我有以下错误未矢量化:未处理的数据参考

你能帮我解释一下为什么第一个代码和第三个代码不是自动矢量化的吗?

第二个问题: 数学操作数似乎没有向量化(exp、log 等...),例如此代码

for (int i = 0; i < iSize; i++)
         pfResult[i] = exp(pfResult[i]);

未矢量化。是因为我的 gcc 版本?

编辑: 使用新版本的 gcc 4.8.1 和 openMP 2011 (echo |cpp -fopenmp -dM |grep -i open) 对于所有类型的循环,我基本上都有以下错误

   for (iGID = 0; iGID < iSize; iGID++)
        
             pfResult[iGID] = fValue;
        


note: not consecutive access *_144 = 5.0e-1;
note: Failed to SLP the basic block.
note: not vectorized: failed to find SLP opportunities in basic block.

编辑2:

#include<stdio.h>
#include<sys/time.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <omp.h>

int main()

        int szGlobalWorkSize = 131072;
        int iGID = 0;
        int j = 0;
        omp_set_dynamic(0);
        // warmup
        #if WARMUP
        #pragma omp parallel
        
        #pragma omp master
        
        printf("%d threads\n", omp_get_num_threads());
        
        
        #endif
        printf("Pagesize=%d\n", getpagesize());
        float *pfResult = (float *)malloc(szGlobalWorkSize * 100* sizeof(float));
        float fValue = 0.5f;
        struct timeval tim;
        gettimeofday(&tim, NULL);
        double tLaunch1=tim.tv_sec+(tim.tv_usec/1000000.0);
        double time = omp_get_wtime();
        int iChunk = getpagesize();
        int iSize = ((int)szGlobalWorkSize * 100) / iChunk;
        //#pragma omp parallel for
        for (iGID = 0; iGID < iSize; iGID++)
        
             pfResult[iGID] = fValue;
        
        time = omp_get_wtime() - time;
        gettimeofday(&tim, NULL);
        double tLaunch2=tim.tv_sec+(tim.tv_usec/1000000.0);
        printf("%.6lf Time1\n", tLaunch2-tLaunch1);
        printf("%.6lf Time2\n", time);

结果

#define _OPENMP 201107
gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-15)

gcc -march=native -fopenmp -O3 -ftree-vectorizer-verbose=2 test.c -lm

很多

note: Failed to SLP the basic block.
note: not vectorized: failed to find SLP opportunities in basic block.
and note: not consecutive access *_144 = 5.0e-1;

谢谢

【问题讨论】:

第一件事确实是尝试更新版本的 gcc。然后意识到没有restrict 向量化可能是错误的。并添加 -ffast-math ,否则编译器会害怕。对于 exp 和 log,我确定我已经看到了有关 SO 的相关问题。基本上,您需要一个库来提供 exp 和 log 的矢量版本,以便 gcc 可以生成对它们的调用。 暂别我之前的评论,你为什么不在循环中使用i??? 非常感谢我已经尝试过使用 'restrict' 和 const,结果是一样的跨度> 我已经安装了 gcc 4.8.1,现在我的所有循环都提供了以下信息说明:无法 SLP 基本块。注意:未矢量化:未能在基本块中找到 SLP 机会。 您的代码无法编译(缺少标头?),这很粗鲁。如果您想在不安装任何东西的情况下测试代码,可以使用在线编译器。 【参考方案1】:

GCC 无法向量化循环的第一个版本,因为它无法证明pfTab[iIndex] 不包含在pfResult[0] ... pfResult[iSize-1] 跨越的内存中的某个位置(指针别名)。实际上,如果pfTab[iIndex] 位于该内存中的某个位置,则其值必须被循环体中的赋值覆盖,并且必须在随后的迭代中使用新值。您应该使用 restrict 关键字来提示编译器这永远不会发生,然后它应该很高兴地矢量化您的代码:

$ cat foo.c
int MyFunc(const float *restrict pfTab, float *restrict pfResult,
           int iSize, int iIndex)

   for (int i = 0; i < iSize; i++)
     pfResult[i] = pfResult[i] + pfTab[iIndex];

$ gcc -v
...
gcc version 4.6.1 (GCC)
$ gcc -std=c99 -O3 -march=native -ftree-vectorizer-verbose=2 -c foo.c
foo.c:3: note: LOOP VECTORIZED.
foo.c:1: note: vectorized 1 loops in function.

第二个版本向量化,因为值被传输到具有自动存储持续时间的变量。这里的一般假设是pfResult 不跨越存储fTab 的堆栈内存(粗略阅读C99 语言规范并不清楚该假设是否弱或标准中的某些内容允许它) .

由于 OpenMP 在 GCC 中实现的方式,OpenMP 版本没有矢量化。它使用并行区域的代码大纲。

int MyFunc(const float *pfTab, float *pfResult, int iSize, int iIndex)

  float fTab =  pfTab[iIndex];
  #pragma omp parallel for
  for (int i = 0; i < iSize; i++)
     pfResult[i] = pfResult[i] + fTab;

实际上变成:

struct omp_data_s

  float *pfResult;
  int iSize;
  float *fTab;
;

int MyFunc(const float *pfTab, float *pfResult, int iSize, int iIndex)

  float fTab =  pfTab[iIndex];
  struct omp_data_s omp_data_o;

  omp_data_o.pfResult = pfResult;
  omp_data_o.iSize = iSize;
  omp_data_o.fTab = fTab;

  GOMP_parallel_start (MyFunc_omp_fn0, &omp_data_o, 0);
  MyFunc._omp_fn.0 (&omp_data_o);
  GOMP_parallel_end ();
  pfResult = omp_data_o.pfResult;
  iSize = omp_data_o.iSize;
  fTab = omp_data_o.fTab;


void MyFunc_omp_fn0 (struct omp_data_s *omp_data_i)

  int start = ...; // compute starting iteration for current thread
  int end = ...; // compute ending iteration for current thread

  for (int i = start; i < end; i++)
    omp_data_i->pfResult[i] = omp_data_i->pfResult[i] + omp_data_i->fTab;

MyFunc_omp_fn0 包含概述的功能代码。编译器无法证明 omp_data_i-&gt;pfResult 没有指向别名为 omp_data_i 的内存,特别是其成员 fTab

为了矢量化该循环,您必须创建fTab firstprivate。这会将其转换为概述代码中的自动变量,这将等同于您的第二种情况:

$ cat foo.c
int MyFunc(const float *pfTab, float *pfResult, int iSize, int iIndex)

   float fTab = pfTab[iIndex];
   #pragma omp parallel for firstprivate(fTab)
   for (int i = 0; i < iSize; i++)
     pfResult[i] = pfResult[i] + fTab;

$ gcc -std=c99 -fopenmp -O3 -march=native -ftree-vectorizer-verbose=2 -c foo.c
foo.c:6: note: LOOP VECTORIZED.
foo.c:4: note: vectorized 1 loops in function.

【讨论】:

以上是关于gcc 自动矢量化(未处理的数据参考)的主要内容,如果未能解决你的问题,请参考以下文章

自动矢量化的实际使用?

cilk 加数组表示法未使用 gcc 4.9.0 进行矢量化

在 GCC 的函数中禁用特定循环的自动矢量化

gcc、clang 和 msvc 的 C++ 自动矢量化要求

使用 gcc 进行自动矢量化?

gcc中的数组与指针自动矢量化