GCC:两个相似循环之间的矢量化差异

时间:2012-08-23 16:55:13

标签: c gcc loops compiler-optimization vectorization

使用gcc -O3进行编译时,为什么以下循环不会自动向量化:

#define SIZE (65536)

int a[SIZE], b[SIZE], c[SIZE];

int foo () {
  int i, j;

  for (i=0; i<SIZE; i++){
    for (j=i; j<SIZE; j++) {
      a[i] = b[i] > c[j] ? b[i] : c[j];
    }
  }
  return a[0];
}

当下一个呢?

#define SIZE (65536)

int a[SIZE], b[SIZE], c[SIZE];

int foov () {
  int i, j;

  for (i=0; i<SIZE; i++){
    for (j=i; j<SIZE; j++) {
      a[i] += b[i] > c[j] ? b[i] : c[j];
    }
  }
  return a[0];
}

唯一的区别是内循环中表达式的结果是分配给[i],还是添加到[i]

供参考-ftree-vectorizer-verbose=6为第一个(非矢量化)循环提供以下输出。

v.c:8: note: not vectorized: inner-loop count not invariant.
v.c:9: note: Unknown alignment for access: c
v.c:9: note: Alignment of access forced using peeling.
v.c:9: note: not vectorized: live stmt not supported: D.2700_5 = c[j_20];

v.c:5: note: vectorized 0 loops in function.

矢量化循环的相同输出是:

v.c:8: note: not vectorized: inner-loop count not invariant.
v.c:9: note: Unknown alignment for access: c
v.c:9: note: Alignment of access forced using peeling.
v.c:9: note: vect_model_load_cost: aligned.
v.c:9: note: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .
v.c:9: note: vect_model_simple_cost: inside_cost = 1, outside_cost = 1 .
v.c:9: note: vect_model_reduction_cost: inside_cost = 1, outside_cost = 6 .
v.c:9: note: cost model: prologue peel iters set to vf/2.
v.c:9: note: cost model: epilogue peel iters set to vf/2 because peeling for alignment is unknown .
v.c:9: note: Cost model analysis:
  Vector inside of loop cost: 3
  Vector outside of loop cost: 27
  Scalar iteration cost: 3
  Scalar outside cost: 7
  prologue iterations: 2
  epilogue iterations: 2
  Calculated minimum iters for profitability: 8

v.c:9: note:   Profitability threshold = 7

v.c:9: note: Profitability threshold is 7 loop iterations.
v.c:9: note: LOOP VECTORIZED.
v.c:5: note: vectorized 1 loops in function.

4 个答案:

答案 0 :(得分:30)

在第一种情况下:代码会在每次迭代中覆盖相同的内存位置a[i]。由于循环迭代不是独立的,因此这本身就会对循环进行顺序化 (实际上,实际上只需要最后的迭代。因此可以取出整个内循环。)

在第二种情况下:GCC将循环识别为缩减操作 - 对其进行矢量化处理的特殊情况。

编译器矢量化通常被实现为某种“模式匹配”。这意味着编译器会分析代码以查看它是否适合它能够进行矢量化的特定模式。如果是,它会被矢量化。如果没有,那就没有。

这似乎是一个极端情况,其中第一个循环不适合GCC可以处理的任何预编码模式。但第二种情况符合“可矢量化减少”模式。


以下是GCC源代码的相关部分,其中列出了"not vectorized: live stmt not supported: "消息:

http://svn.open64.net/svnroot/open64/trunk/osprey-gcc-4.2.0/gcc/tree-vect-analyze.c

if (STMT_VINFO_LIVE_P (stmt_info))
{
    ok = vectorizable_reduction (stmt, NULL, NULL);

    if (ok)
        need_to_vectorize = true;
    else
        ok = vectorizable_live_operation (stmt, NULL, NULL);

    if (!ok)
    {
        if (vect_print_dump_info (REPORT_UNVECTORIZED_LOOPS))
        {
            fprintf (vect_dump, 
                "not vectorized: live stmt not supported: ");
            print_generic_expr (vect_dump, stmt, TDF_SLIM);
        }
        return false;
    }
}

从这一行开始:

vectorizable_reduction (stmt, NULL, NULL);

很明显,GCC正在检查它是否与“可矢量化减少”模式匹配。

答案 1 :(得分:3)

GCC矢量化器可能不够智能,无法对第一个循环进行矢量化。添加案例更容易矢量化,因为a + 0 == a。考虑SIZE==4

  0 1 2 3 i
0 X
1 X X
2 X X X
3 X X X X
j

X表示ija分配或增加时的组合。对于添加的情况,我们可以为b[i] > c[j] ? b[i] : c[j]j==1计算i==0..4的结果,并将其放入向量D。然后我们只需将D[2..3]归零并将结果向量添加到a[0..3]。对于分配的情况,它更棘手。我们不仅要将D[2..3]归零,还要归零A[0..1],然后才能合并结果。我想这就是矢量化器失败的地方。

答案 2 :(得分:3)

第一个循环相当于

#define SIZE (65536)

int a[SIZE], b[SIZE], c[SIZE];

int foo () {
  int i, j;

  for (i=0; i<SIZE; i++){
    a[i] = b[i] > c[SIZE - 1] ? b[i] : c[SIZE - 1];
  }
  return a[0];
}

原始表达式的问题在于它确实没那么有意义,因此gcc无法对其进行矢量化并不太令人惊讶。

答案 3 :(得分:1)

第一个只是简单地多次改变[](临时)。 第二个每次使用[]的最后一个值(不是临时的)。

在修补程序版本之前,您可以使用“volatile”变量进行矢量化。

使用

int * c=malloc(sizeof(int));

使其对齐;

v.c:9: note: Unknown alignment for access: c

显示“c”具有与b和a不同的存储区域。

我假设“movaps”式的“向量化”指令(来自SSE-AVX指令列表)

此处:http://gcc.gnu.org/projects/tree-ssa/vectorization.html#using

第六和第七个例子显示出类似的困难。