GCC 自动矢量化对运行时没有影响,即使在所谓“有利可图”的情况下也是如此

Posted

技术标签:

【中文标题】GCC 自动矢量化对运行时没有影响,即使在所谓“有利可图”的情况下也是如此【英文标题】:GCC auto-vectorization has no effect on runtime, even when supposedly "profitable" 【发布时间】:2015-01-12 18:55:11 【问题描述】:

过去几天我一直在阅读有关使用 gcc 4.7 进行自动矢量化的文章。我按照我在网上看到的一些例子,设置似乎是正确的。但是当我实际使用代码运行并比较矢量化打开或关闭时,运行时没有明显差异。

这是我一直在使用的代码:

#include <string.h>
#include <stdlib.h>
#include <emmintrin.h>
#include <stdio.h>
#include <math.h>

int main(int argc, char** argv) 

    long b = strtol(argv[2], NULL, 0); 
    unsigned long long int i;
    unsigned long long int n = (int)pow(2,29);                                                                                                                                                                                            
    float total = 0;

    float *__restrict__ x1; 
    float *__restrict__ y1; 

    posix_memalign((void *)&x1, 16, sizeof(float)*n);
    posix_memalign((void *)&y1, 16, sizeof(float)*n);


    float *__restrict__ x = __builtin_assume_aligned(x1,16);
    float *__restrict__ y = __builtin_assume_aligned(y1,16);

    for (i=0;i<n;i++) 
            x[i] = i;
            y[i] = i;
       

    for (i=0; i<n; i++) 
            y[i] += x[i];
       

    printf("y[%li]: \t\t\t\t%f\n",  b,y[b]);
    printf("correct answer: \t\t\t%f\n", (b)*2);
    return 0;

其中一些东西对我来说似乎是多余的,但对于让编译器了解正在发生的事情是必要的(尤其是数据对齐的事实)。从命令行读取的“b”变量就在那里,因为我对编译器完全优化循环感到偏执。

这是启用矢量化时的编译器命令:

gcc47 -ftree-vectorizer-verbose=3 -msse2 -lm -O2 -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -fipa-cp-clone test.c -ftree-vectorize -o v

基本上,这相当于只使用 -O3。我把标志放在自己身上,所以我需要做的就是删除“ftree-vectorize”并能够在没有矢量化的情况下测试结果。

这是 ftree-vectorize-verbose 标志的输出,以表明代码实际上正在被矢量化:

Analyzing loop at test.c:29

29: vect_model_load_cost: aligned.
29: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .
29: vect_model_load_cost: aligned.
29: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .
29: vect_model_simple_cost: inside_cost = 1, outside_cost = 0 .
29: vect_model_store_cost: aligned.
29: vect_model_store_cost: inside_cost = 1, outside_cost = 0 .
29: cost model: Adding cost of checks for loop versioning aliasing.

29: Cost model analysis: 
  Vector inside of loop cost: 4
  Vector outside of loop cost: 4
  Scalar iteration cost: 4
  Scalar outside cost: 1
  prologue iterations: 0
  epilogue iterations: 0
  Calculated minimum iters for profitability: 2

29:   Profitability threshold = 3


Vectorizing loop at test.c:29

29: Profitability threshold is 3 loop iterations.
29: created 1 versioning for alias checks.

29: LOOP VECTORIZED.
Analyzing loop at test.c:24

24: vect_model_induction_cost: inside_cost = 2, outside_cost = 2 .
24: vect_model_simple_cost: inside_cost = 2, outside_cost = 0 .
24: not vectorized: relevant stmt not supported: D.5806_18 = (float) D.5823_58;

test.c:7: note: vectorized 1 loops in function.

请注意,矢量化在 3 次迭代后是有利可图的,并且我正在运行 2^29~=500,000,000 次迭代。所以我应该期待一个完全不同的运行时关闭矢量化,对吧?

好吧,这里是代码的运行时间(我连续运行了 20 次):

59.082s                                                                                                                                                                                                                                       
79.385s
57.557s
57.264s
53.588s
54.300s
53.645s
69.044s
57.238s
59.366s
56.314s
55.224s
57.308s
57.682s
56.083s
369.590s
59.963s
55.683s
54.979s
62.309s

去掉那个奇怪的约 370 秒的异常值,平均运行时间为 58.7 秒,标准差为 6.0 秒。

接下来,我将使用与之前相同的命令进行编译,但没有 -ftree-vectorize 标志:

gcc47 -ftree-vectorizer-verbose=3 -msse2 -lm -O2 -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -fipa-cp-clone test.c -o nov

再次连续运行程序 20 次会产生以下次数:

69.471s                                                                                                                                                                                                                                       
57.134s
56.240s
57.040s
55.787s
56.530s
60.010s
60.187s
324.227s
56.377s
55.337s
54.110s
56.164s
59.919s
493.468s
63.876s
57.389s
55.553s
54.908s
56.828s

再次丢弃异常值,平均运行时间为 57.9 秒,标准差为 3.6 秒。

所以这两个版本的运行时间在统计上无法区分。

谁能指出我做错了什么?编译器吐出的“盈利门槛”不是我想的意思吗?我非常感谢人们能给我的任何帮助,过去一周我一直在努力解决这个问题。

编辑

我实施了@nilspipenbrinck 建议的更改,并且似乎奏效了。我将矢量化循环卡在一个函数中,并多次调用该函数。相对运行时间现在为 24.0 秒(sigma 为

【问题讨论】:

是否有任何特殊原因要对过时的编译器版本进行测试?你的硬件是什么? 参考局部性对现代处理器来说很重要。你的程序没有,它的运行时间完全由访问 RAM 的成本决定。 @Drop 我打算在我实验室的多台不同的计算机上运行它,它们都使用这个编译器。实际上,他们使用的版本比这更旧,gcc4.4 或更低。但是我想试验的一些功能直到 4.7 才出现。我没有能力升级编译器,或者我会。 @HansPassant 感谢您的建议,我现在正在查找有关参考地点的信息。 这是一个重复的问题***.com/questions/18159455/… 【参考方案1】:

你不会做很多算术。因此,您的测试代码的运行时间是受内存限制的。例如。您大部分时间都花在在 CPU 和内存之间移动数据上。

此外,您的 n 非常大,有 2^29 个元素。因此,您不会以任何方式从一级和二级缓存中受益。

如果您想看到 SSE 的改进,请使用较小的 n,以便您只接触 8 或 16 KB 的数据。还要确保数据是“热的”,例如它最近被 CPU 访问过。这样,数据不必从主内存中移出,而是从缓存中移出,速度要快几个数量级。

作为替代方案,您还可以进行 很多 更多的算术运算。这将使内存预取系统有机会在您利用 CPU 进行数学运算时在后台从主内存中获取数据。

总结:如果算术比您的系统移动内存的速度更快,您将看不到任何好处。内存访问时间将成为瓶颈,您使用 SSE 指令集节省的几个周期将在内存访问时间的噪音中消失。

【讨论】:

啊,这很有意义。对 8 KB 的数据进行操作实际上并没有可测量的运行时间,但我可以将向量化循环粘贴在一个函数中,然后调用该函数一百万次。谢谢你的建议,我马上去试试。 @user2635263 可以测量运行时间。只需使用可以通过 RDTSC 指令读出的循环计数器。谷歌一下,你会找到基准框架。 虽然说这是内存带宽限制是正确的,但这并不意味着它不能显着改善。如果 OP 使用多个线程和非临时存储,他/她可能会订购两倍的改进。【参考方案2】:

有几个因素决定了矢量化代码的盈利能力。在这种情况下(基于您提供的输出),编译器仅对一个循环进行矢量化,我认为这是第二个循环,因为第一个循环通常会被忽略,因为没有进行足够的计算使其对矢量化有利可图.

您发布的运行时间是针对整个代码的,而不是单独针对循环的,因此对于整体运行时间而言,矢量化只需要这么多。如果你真的想看看矢量化有多少改进,我建议运行一个分析器,如 AMD Code XL、Intel Vtune、OProfile 等,它会专门告诉你该循环在时间和性能方面有多大改进正在制作中。

现在我正在评估矢量化编译器,我将通过矢量化将代码运行速度提高 60 倍,其他时候速度提升并不那么令人印象深刻,这完全取决于循环、编译器和架构你正在使用。

【讨论】:

知道了。感谢您的回答。自从发布这篇文章以来,我学到了很多关于代码矢量化的知识!

以上是关于GCC 自动矢量化对运行时没有影响,即使在所谓“有利可图”的情况下也是如此的主要内容,如果未能解决你的问题,请参考以下文章

使用 GCC 强制自动矢量化

除了 gcc 还都有哪些编译器可以向量化代码?

当数组是函数参数时,矩阵乘法中的 Gcc 自动向量化奇怪行为

自动矢量化感兴趣区域(裁剪)

双和ffast数学的自动矢量化

使用 GCC 和 GFORTRAN 进行矢量化