gcc自动向量化(未处理的数据引用)

3

我不明白为什么这样的代码在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;
}

如果我添加omp指令,gcc就会成功地自动向量化此循环。

   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

许多(关于IT技术)
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就可以生成对它们的调用。 - Marc Glisse
忽略我之前的评论,为什么你的循环中没有使用 i??? - Marc Glisse
我已经尝试过“__restrict__”和const,但结果是相同的。我将尝试更高版本的gcc。循环中的拼写错误很抱歉。谢谢! - parisjohn
我已经安装了gcc 4.8.1,现在我的所有循环都会给出以下信息: 注意:无法SLP基本块。 注意:未向量化:无法在基本块中找到SLP机会。 - parisjohn
1
你的代码无法编译(缺少头文件?),这太不礼貌了。如果你想测试代码而又不想安装任何东西,可以使用在线编译器。 - Marc Glisse
显示剩余6条评论
1个回答

7

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->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.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接