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

Posted

技术标签:

【中文标题】GCC:两个相似循环之间的向量化差异【英文标题】:GCC: vectorization difference between two similar loops 【发布时间】:2012-08-19 06:17:43 【问题描述】:

使用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];

唯一的区别是内循环中表达式的结果是赋值给a[i],还是添加到a[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.

【问题讨论】:

如果将 int j 声明移动到外部 i 循环内会发生什么? 这是什么版本的 GCC? x86 还是 x64?我无法在我的 32 位操作系统上重现它。即将启动我的另一台机器进行测试。 gcc --version 给出“gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 C”,uname -a 给出“Linux ubuntu-dev 3.0.0-12-virtual #20- Ubuntu SMP 2011 年 10 月 7 日星期五 18:19:02 UTC x86_64 x86_64 x86_64 GNU/Linux" 乔希,显然在哪里声明 j 并不重要,同样的结果。 我们去,设法复制它。它必须是 x64。 【参考方案1】:

第一种情况:代码在每次迭代中覆盖相同的内存位置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 正在检查它是否匹配“矢量化缩减”模式。

【讨论】:

您是否发现 gcc 能够对第一种情况进行矢量化,并将(大部分冗余的)内部循环替换为最终迭代? 是的。它能够矢量化它。 :) 我花了一段时间才注意到冗余。当我看到这个问题并忽略了显而易见的问题时,我有点直接跳到金属上。【参考方案2】:

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 表示ij 的组合,此时a 将被分配或增加。对于加法的情况,我们可以计算b[i] &gt; c[j] ? b[i] : c[j] 的结果,比如j==1i==0..4,并将其放入向量D。然后我们只需要将D[2..3] 归零并将结果向量添加到a[0..3]。对于分配的情况,它有点棘手。我们不仅必须将D[2..3] 归零,还必须将A[0..1] 归零,然后才能合并结果。我想这就是矢量化器失败的地方。

【讨论】:

当然不是。然后向量化器将确保所有存储和加载都以与非向量化循环相同的顺序发生。事实上,添加 volatile 会使 GCC 也不会向量化第二个循环。【参考方案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 不能对其进行向量化也就不足为奇了。

【讨论】:

+1 编译器通常不擅长优化“糟糕”的代码。拥有完整的冗余循环就是这样的例子之一。【参考方案4】:

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

在补丁版本之前,您可以使用“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

第 6 和第 7 个示例显示了类似的困难。

【讨论】:

两个版本都存在未知对齐问题。所以我怀疑情况是否如此。 @Mystical 是正确的,请参阅添加到上述问题的附加输出。 @laslowh:好的,我正在添加东西。 什么是new?) 它返回的东西是否与int 兼容?

以上是关于GCC:两个相似循环之间的向量化差异的主要内容,如果未能解决你的问题,请参考以下文章

如何量化两个图像之间的差异?

我们是不是需要 C++ 中的向量化或 for 循环已经足够快?

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

MATLAB中结构多级索引的向量化

文本相似性计算

1. 文本相似度计算-文本向量化