英特尔编译器(ICC)无法自动向量化内循环(矩阵乘法)

时间:2018-03-08 16:46:44

标签: c++ c performance vectorization compiler-optimization

编辑:

ICC(在添加-qopt-report = 5 -qopt-report-phase:vec之后):

LOOP BEGIN at 4.c(107,2)
   remark #15344: loop was not vectorized: vector dependence prevents vectorization
   remark #15346: vector dependence: assumed OUTPUT dependence between c[i][j] (110:5) and c[i][j] (110:5)
   remark #15346: vector dependence: assumed OUTPUT dependence between c[i][j] (110:5) and c[i][j] (110:5)

   LOOP BEGIN at 4.c(108,3)
      remark #15344: loop was not vectorized: vector dependence prevents vectorization
      remark #15346: vector dependence: assumed OUTPUT dependence between c[i][j] (110:5) and c[i][j] (110:5)
      remark #15346: vector dependence: assumed OUTPUT dependence between c[i][j] (110:5) and c[i][j] (110:5)

      LOOP BEGIN at 4.c(109,4)
         remark #15344: loop was not vectorized: vector dependence prevents vectorization
         remark #15346: vector dependence: assumed FLOW dependence between c[i][j] (110:5) and c[i][j] (110:5)
         remark #15346: vector dependence: assumed ANTI dependence between c[i][j] (110:5) and c[i][j] (110:5)
      LOOP END

      LOOP BEGIN at 4.c(109,4)
      <Remainder>
      LOOP END
   LOOP END
LOOP END

似乎C [i] [j]在写入之前被读取,如果是矢量化的(因为我正在进行缩减)。问题是为什么允许减少是引入局部变量(temp)?

原始问题:

我下面有一个C片段,用于矩阵乘法。 a,b - 操作数,c - a * b结果。 n - 行和列长度。

double ** c = create_matrix(...) // initialize n*n matrix with zeroes
double ** a = fill_matrix(...) // fills n*n matrix with random doubles
double ** b = fill_matrix(...) // fills n*n matrix with random doubles

for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++) {
        for (k = 0; k < n; k++) {
            c[i][j] += a[i][k] * b[k][j];
        }
    }
}

ICC(版本18.0.0.1)无法对内循环进行向量化(提供-O3标志)。

ICC输出:

LOOP BEGIN at 4.c(107,2)
   remark #25460: No loop optimizations reported

   LOOP BEGIN at 4.c(108,3)
      remark #25460: No loop optimizations reported

      LOOP BEGIN at 4.c(109,4)
         remark #25460: No loop optimizations reported
      LOOP END

      LOOP BEGIN at 4.c(109,4)
      <Remainder>
      LOOP END
   LOOP END
LOOP END

尽管如此,通过以下更改,编译器会对内部循环进行矢量化。

// OLD
for (k = 0; k < n; k++) {
  c[i][j] += a[i][k] * b[k][j];
}

// TO (NEW)
double tmp = 0;

for (k = 0; k < n; k++) {
    tmp += a[i][k] * b[k][j];
}

c[i][j] = tmp;

ICC矢量化输出:

LOOP BEGIN at 4.c(119,2)
   remark #25460: No loop optimizations reported

   LOOP BEGIN at 4.c(120,3)
      remark #25460: No loop optimizations reported

      LOOP BEGIN at 4.c(134,4)
      <Peeled loop for vectorization>
      LOOP END

      LOOP BEGIN at 4.c(134,4)
         remark #15300: LOOP WAS VECTORIZED
      LOOP END

      LOOP BEGIN at 4.c(134,4)
      <Alternate Alignment Vectorized Loop>
      LOOP END

      LOOP BEGIN at 4.c(134,4)
      <Remainder loop for vectorization>
      LOOP END
   LOOP END
LOOP END

不是在矩阵C单元格中累加向量乘法结果,而是将结果累积在一个单独的变量中并稍后分配。

为什么编译器不优化第一个版本?可能是由于a或/和b到c元素的潜在混叠(写入后读取问题)?

1 个答案:

答案 0 :(得分:1)

利用您的编译器

您不会显示您用于获取矢量化报告的标记。我建议:

-qopt-report=5 -qopt-report-phase:vec

文档说:

  

对于n = 1到n = 5的级别,每个级别包括前一级别的所有信息,以及可能的一些附加信息。 5级产生最大程度的细节。如果未指定n,则默认值为2级,这将产生中等级别的详细信息。

随着更高级别的细节,编译器可能告诉你(用神秘的术语)为什么它没有矢量化。

我怀疑编译器担心内存是别名。您找到的解决方案允许编译器证明它不是,因此它执行矢量化。

便携式解决方案

如果您使用的是OpenMP,则可以使用:

#pragma omp simd
for (k = 0; k < n; k++)
  c[i][j] += a[i][k] * b[k][j];

完成同样的事情。我认为英特尔还有一组特定于编译器的指令,这些指令将以非可移植的方式执行此操作。

其他注释

这一行:

double ** c = create_matrix(...)

让我紧张它表明在某个地方你有这样的东西:

for(int i=0;i<10;i++)
  c[i] = new double[20];

也就是说,你有一个数组数组。

问题是,这并不保证您的子数组在内存中是连续的。结果是次优缓存利用率。您想要一个像2D数组一样寻址的一维数组。制作一个2D数组类来执行此操作或使用函数/宏来访问元素将允许您保留大致相同的语法,同时从更好的缓存性能中受益。

您可能还会考虑使用-align标记进行编译并适当地修改代码。通过允许对内存进行对齐访问,这将提供更好的SIMD性能。