Part1. 加载/存储组的向量化

GCC 5.0 显著的提升了 vector
向量的加载和存储组的代码质量,我这里说的是连续顺序的迭代,例如:

x = a[i], y = a[i + 1], z = a[i + 2] 通过 i 进行迭代,加载了大小为
3 的组

组大小由加载和存储地址的最大和最小值来确定,例如 (i + 2) – (i) + 1 = 3

组中加载和存储的次数小于和等于组的大小,例如:

x = a[i], z = a[I + 2] 通过 i 进行迭代,尽管只有 2
次加载,但是加载组的大小为 3

GCC 4.9 向量组的大小是 2 的指数,而 GCC 5.0 向量化组的大小是 3
,也可以是 2 的指数,其他的组大小使用比较少。

最常用加载和存储组的场景是结构数组。

  1. 图像转换 (例如将 RGB 结构转为其他)

    (场景测试 )

  2. 多维的坐标 (测试场景
    )

  3. 向量的乘法常数矩阵

a[i][0] = 7 * b[i][0] – 3 * b[i][1];
a[i][1] = 2 * b[i][0] + b[i][1];

基本上 GCC 5.0 给我们带来了:

  1. 引入大小为 3 的向量加载和存储组

  2. 改进对原有支持的其他组大小

  3. 通过为特定的 x86 CPU 优化的代码来最大化加载和存储组性能

下面是一个用来比较 GCC 4.9 和 GCC 5.0
性能的一段代码(最大化向量中的元素个数)

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i, j, k; 
byte *in = a, *out = b;
for (i = 0; i < 1024; i++)
{
  for (k = 0; k < STGSIZE; k++)
    {
      byte s = 0;
      for (j = 0; j < LDGSIZE; j++)
        s += in[j] * c[j][k];
      out[k] = s;
    }
  in += LDGSIZE;
  out += STGSIZE;
}

而 “c” 是一个固定的矩阵:

?

1
2
3
4
5
6
7
8
const byte c[8][8] = {1, -1, 1, -1, 1, -1, 1, -1,
                      1, 1, -1, -1, 1, 1, -1, -1,
                      1, 1, 1, 1, -1, -1, -1, -1,
                      -1, 1, -1, 1, -1, 1, -1, 1,
                      -1, -1, 1, 1, -1, -1, 1, 1,
                      -1, -1, -1, -1, 1, 1, 1, 1,
                      -1, -1, -1, 1, 1, 1, -1, 1,
                      1, -1, 1, 1, 1, -1, -1, -1};

在循环中使用简单的计算,例如 add、sub 等操作速度非常快。

  • “in“ 和 “out” 指针指向全局数组 “a[1024 * LDGSIZE]” 和 “b[1024 *
    STGSIZE]”

  • byte 是一个无符号的 char

  • LDGSIZE 和 STGSIZE – 根据组大小定义加载和存储组的宏

编译选项 “-Ofast” 加 “-march=slm” 用于 Silvermont, “-march=core-avx2”
用于 Haswell 以及所有合并 -DLDGSIZE={1,2,3,4,8} -DSTGSIZE={1,2,3,4,8}

GCC 5.0 到 4.9 (时间上,越大越好)

Silvermont: Intel(R) Atom(TM) CPU  C2750  @ 2.41GHz

性能提升 6.5 倍

澳门新葡亰8455下载app 1

上 表结果我们可以看到组大小为 3 的时候结果没那么好。因为当组大小为 3
时在 Slivermont 上需要 8 个 pshufb 指令和大约 5 个
ticks。当然循环依然是向量化的,如果在循环内有更消耗 CPU
的计算,那么效果还是会很不错。(我们再看其他组大小)

Haswell: Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz

性能提升 3 倍!

澳门新葡亰8455下载app 2

在 Haswell 上组大小为 3 的时候只需 1 个
tick。我们能看到最大的提升就是组大小为 3 的时候。

上述的实验你可以通过下面地址获取相应编译器

GCC 4.9:

GCC 5.0 trunk built at revision 217914:

Download
澳门新葡亰8455下载app 3matrix.zip

via
intel

(文/开源中国)    

可以自动矢量化的代码


使用gcc version 4.3.4的gcc编译器,编译下面的示例代码:

int main(){

  const unsigned int ArraySize = 10000000;

  float* a = new float[ArraySize];

  float* b = new float[ArraySize];

  float* c = new float[ArraySize];

  for (unsigned int j = 0; j< 200 ; j++) // some repetitions

    for ( unsigned int i = 0; i < ArraySize; ++ i)

        c[i] = a[i] * b[i];

}

编译并运行上面的代码:

g++ vectorisable_example.cpp -o vectorisable_example -O2 time ./vectorisable_example

简单,现在我们看下如何告诉 compiler 自动对代码进行矢量化:

g++ vectorisable_example.cpp -o vectorisable_example_vect -O2 -ftree-vectorize time ./vectorisable_example_vect

第二种编译方式,产生的可执行程序比第一种产生的可执行程序运行的更快?我们深入研究下,看看为什么他就这么快?

简介


从上世纪九十年代后期开始,英特尔就已经将单指令多数据(SIMD)机器指令集成到其商品CPU产品线中。这些SIMD指令允许一次将相同的数学运算(乘,加…)应用于2,4或甚至更多的数据值上。这组数据值也被称为“vector”(不要与代数中的vector混淆)。理论上矢量计算的性能增益等于CPU可以容纳的矢量单位的数量。

尽管SIMD指令集已经集成到普通CPU中相当长一段时间了,但是这些扩展功能(SIMD指令集)只能通过在C或者C++代码中通过准汇编语言进行使用。像GNU编译器系列(GCC)这样的现代编译器现在可以将常规的C或C
++源代码转换成向量操作。这个过程被称为自动矢量化(auto-vectorization)。
这篇文章,我们将会介绍必要的软件工具和编程技术,并进一步提供利用GCC自动矢量化功能的示例源代码

硬件


要估算自动矢量化代码可以带来多大的性能提升,首先需要了解您正在使用的CPU的能力以及CPU可以处理多少个Vector。因此,运行以下命令:

$ cat /proc/cpuinfo…
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat
pse36 clflush dts acpi mmx fxsr sse sse2ss ht tm pbe syscall
nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl
xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl
vmx est tm2ssse3cx16 xtpr pdcmsse4_1 sse4_2x2apic popcnt
xsaveavxlahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi
flexpriority ept vpid…

在CPU属性中,您将找到有关SIMD功能的信息(以粗体突出显示)。若信息中包含下表列出的字符串,则您的CPU支持(SIMD)功能:

Name Register Size Amount of floats Amount of doubles
mmx (*) 64 bit 0 0
sse2 128 bit 4 2
ssse3 128 bit 4 2
sse4_1 128 bit 4 2
sse4_2 128 bit 4 2
avx 256 bit 8 4
  • 英特尔处理器上的第一个矢量单元只能容纳两个迭代器。

所有Intel和AMD 64位CPU都至少支持sse2指令集。
AVX指令集已于2011年推出,采用Sandy Bridge架构,可在最新一代Mac Book
Pro笔记本电脑上使用。
AVX指令集可以同时对四个双精度浮点值应用数学运算。因此,与未使用矢量单位的版本相比,应用程序可以实现的最大性能增益是4倍。
Vector单位大小的持续增大,反应了是计算机微体系结构演化的新趋势。十年前,计算机的时钟频率几乎每年翻一番。计算机的时钟频率已经达到了极限,制造商转而开始提供越来越大的矢量单位,并且在每个芯片中提供越来越多的核心数量。

哪些代码可以被auto-vectorized ?


为了将计算分布到CPU的矢量单元,编译器必须对源代码的依赖性和相互影响有一个很好的理解。但最重要的是,编译器必须能够检测出那些代码段可以使用SIMD指令进行优化。最简单的情况是对数组执行计算的循环:

for ( unsigned int i = 0; i < ArraySize; ++ i)
{
      c[i] = a[i] * b[i];
}

使用等于或者高于gcc461(在slc5_amd64_gcc461
scram体系结构的服务器中可用)版本的gcc编译器,编译的时候,一定要带上is-ftree-vectorize澳门新葡亰8455下载app,。

注意:从自动矢量化中获益最多的循环是包含数学运算的循环。只是迭代对象集合的循环不会获利,在大多数情况下甚至不能自动矢量化。

可以在这里找到可以自动矢量化的循环结构列表:http://gcc.gnu.org/projects/tree-ssa/vectorization.html
一旦你使用选项-ftree-vectorizer-verbose =
7
编译你的代码,GCC将会给你一个关于你的程序中所有循环的详细报告,以及它们是否已经被自动矢量化了。以下报告是成功对循环进行向量化的结果:

autovect.cpp:66: note: vect_model_load_cost: aligned.

autovect.cpp:66: note: vect_get_data_access_cost: inside_cost = 1, outside_cost = 0.

autovect.cpp:66: note: vect_model_load_cost: aligned.

autovect.cpp:66: note: vect_get_data_access_cost: inside_cost = 2, outside_cost = 0.

autovect.cpp:66: note: vect_model_store_cost: aligned.

autovect.cpp:66: note: vect_get_data_access_cost: inside_cost = 3, outside_cost = 0.

autovect.cpp:66: note: vect_model_load_cost: aligned.

autovect.cpp:66: note: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .

autovect.cpp:66: note: vect_model_load_cost: aligned.

autovect.cpp:66: note: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .

autovect.cpp:66: note: vect_model_simple_cost: inside_cost = 1, outside_cost = 0 .

autovect.cpp:66: note: vect_model_simple_cost: inside_cost = 1, outside_cost = 1 .

autovect.cpp:66: note: vect_model_store_cost: aligned.

autovect.cpp:66: note: vect_model_store_cost: inside_cost = 1, outside_cost = 0 .

autovect.cpp:66: note: Cost model analysis:

Vector inside of loop cost: 5

Vector outside of loop cost: 11

Scalar iteration cost: 5

Scalar outside cost: 0

prologue iterations: 0

epilogue iterations: 2

Calculated minimum iters for profitability: 3

autovect.cpp:66: note:  Profitability threshold = 3

autovect.cpp:66: note: Profitability threshold is 3 loop iterations.

autovect.cpp:66: note: LOOP VECTORIZED.

若一个循环不能被向量化, GCC将会给出原因:

autovect.cpp:133: note: not vectorized: control flow in loop.

上面的输出的含义是:在循环中调用了tostd ::
cout,引入了一个控制流,从而无法对该循环进行向量化。

让GCC自动对你的代码进行向量化


想要让GCC对自己的代码进行自动向量化,需要使用最新的编译器,建议使用至少GCC
4.6.1
版本的编译器,通常来说,版本越新越好。

编译器标示


想要打开auto-vectorization,使用标示:

-ftree-vectorize

如果您使用优化级别-O3or进行编译,则隐式启用此选项。
想要得到哪些loop是已经被auto-vectorized和哪些loops没有被成功矢量化以及原因,可以使用选项:

-ftree-vectorizer-verbose=7

关于如何阅读这个输出,请看下面的Real-World代码示例部分。
某些循环结构(例如浮点数的减法)只能在允许编译器改变数学运算顺序的情况下进行矢量化。要做到这一点,需要使用选项:

-ffast-math

如果您使用优化级别-Ofast,则会隐式启用该选项。请注意,fast-math选项会修正修改浮点运算中的错误操作。详情请看http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Optimize-Options.html
此外,您可以明确指出编译器在生成二进制文件时应使用哪一个SIMD指令集。在编译x86-64架构时,GCC默认使用SSE2指令集。如果要启用AVX指令集,请使用编译器标志:

-mavx

需要注意的是,要运行二进制文件的机器必须支持AVX指令集。您还可以让通过下面的选项,让编译器自己决定使用机器中的哪个指令集:

-march=native

若你想要使用 C++11 的特性,例如:lambda 表达式,一定要确认开启了新的C++
Standard:

-std=gnu++0x

启用auto-vectorization最佳实践


网站地图xml地图