【问题标题】:Intel compiler (ICC) unable to auto vectorize inner loop (matrix multiplication)英特尔编译器 (ICC) 无法自动矢量化内循环(矩阵乘法)
【发布时间】:2018-08-17 02:39:45
【问题描述】:

编辑:

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 sn-p 下面可以进行矩阵乘法。 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 元素的潜在别名(写后读问题)?

【问题讨论】:

  • 请提供minimal reproducible example。在您在这里显示的代码中,abc 甚至可能指的是完全相同的内存
  • 我同意 a、b 和 c 可以引用相同的确切内存。这是我对为什么块没有矢量化的假设。但是,为什么当我引入单独的累加器时它会被矢量化?我从不让 c a b 引用相同的内存位置。虽然编译器可能认为我可能会这样做。
  • 编译器必须生成正确的代码,即使 a 或 b 和 c 重叠,除非它可以证明这不会发生。如果你提供一个完整的例子,将会有详细的解释。
  • 好建议。我已经用初始化示例更新了代码。
  • 我已经编辑了我的帖子。似乎问题在于数据依赖性。

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


【解决方案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 数组类或使用函数/宏访问元素将允许您保留大致相同的语法,同时受益于更好的缓存性能。

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

【讨论】:

  • 谢谢你,理查德。 ICC 现在告诉“……假定……之间的 FLOW 依赖关系”。编译器似乎怀疑 c[i][j] 在赋值的左侧和右侧之间的依赖关系。据我了解,结果之间存在重叠
  • @Lukas:您的问题是“为什么编译器不优化第一个版本?”我试图用我的回答含蓄地表达的是,这个问题真的不值得问或回答(除非你认为你犯了一个错误,但这是一个微妙不同的问题)。其他版本的 ICC 或不同的编译器可能已成功执行此操作!了解为什么编译器做某事主要是编译器开发人员感兴趣的,能够理解编译器做了什么以及如何解决它是一个更有用的知识。因此,我专注于这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-03
  • 2013-11-11
  • 1970-01-01
相关资源
最近更新 更多