Vectorization 向量化编程

本文是一篇向量化编程入门文章。

Vectorization is the process of converting an algorithm from a scalar implementation, which does an operation one pair of operands at a time, to a vector process where a single instruction can refer to a vector (a series of adjacent values).

与通常的标量程序不同,向量化的程序可以一个指令处理一个向量(若干个相邻值),是指令级并行的技术。向量化可以分为由编译器自动完成的自动向量化和由开发者显式调用向量化指令完成的显式向量化。

自动向量化

自动向量化由编译器尝试自动使用 SIMD 指令。用户也可以提供一些附加信息(如 hint 和 pragma)来提示编译器。

编程指引

  1. 适用简单的 for 循环,避免复杂的循环终止条件
  2. straight-line code,避免分支语句
  3. 避免数据 dependency
  4. 尽量使用数组类型,不要通过指针来访问数组
  5. 通过 loop index 访问数组,不要通过单独的自增变量作为下标访问数组
  6. 16-byte 对齐(针对 SSE 指令集)
  7. 使用 structure of arrays(SoA),而不是 array of structures(AoS)

性能测试

const int repeat_times = 1000000;
const int sz = 8192;

int a[sz], b[sz], c[sz];
int foo () {
  for (int i=0; i<sz; i++){ // loop vectorized
    a[i] = b[i] + c[i];
  }

  int sum = 0;
  for (int i=0; i<sz; i++){ // loop vectorized
    sum += a[i];
  }
  return sum;
}

int main() {
  int sum = 0;
  for(int i = 0; i < repeat_times; ++i) {
    sum += foo();
  }
  return sum;
}

例子是一个简单的求和函数,我们通过这个例子来简单验证自动向量化的性能表现。通过设置 -fopt-info-vec-optimized,在屏幕上打印编译器的优化信息。实际上,-fopt-info-vec- 可以支持 all、note、optimized 多个级别。

> g++ main.cc -fopt-info-vec-optimized -mavx512f  -O3 -o a5.out
main.cc:11:18: optimized: loop vectorized using 64 byte vectors
main.cc:6:18: optimized: loop vectorized using 64 byte vectors
main.cc:11:18: optimized: loop vectorized using 64 byte vectors
main.cc:6:18: optimized: loop vectorized using 64 byte vectors
> time ./a5.out
./a5.out  0.79s user 0.00s system 99% cpu 0.800 total

通过不同的编译选项,得到最终的执行结果如下,可以看到向量化对于性能的影响还是非常显著的。

编译选项 执行时间 优化方式
-O3 -fno-tree-vectorize 10.31s
-O3 2.62s loop vectorized using 16 byte vectors
-msse4.2 -O3 2.53s loop vectorized using 16 byte vectors
-mavx2 -O3 1.44s loop vectorized using 32 byte vectors
-mavx512f -O3 0.79s loop vectorized using 64 byte vectors

无法向量化的场景

  • 不连续的内存访问
    4个相邻的整形或者浮点型数值,或2个相邻的双精度浮点型数值,可以通过一条 SSE 指令加载。在内存中的不相邻的数据,若通过 SIMD 指令访问可能会加载额外的数据。甚至,对于循环步长超过指令集位宽的场景,无法通过向量化加速。
obstacle.cc
const int SIZE = 1024;
int fun(double a[1024], double b[1024], double x[1024], int index[1024]) {
  // arrays accessed with stride 2
  for (int i = 0; i < SIZE; i += 3) b[i] += a[i] * x[i];
  return b[10];
}
gcc -c obstacle.cc -fopt-info-vec-all -O3 -msse
obstacle.cc:5:21: missed: couldn't vectorize loop
obstacle.cc:5:21: missed: Loop costings may not be worthwhile.
obstacle.cc:3:5: note: vectorized 0 loops in function.
  • Data Dependency
  1. 无依赖,每次循环写的变量都不一样
  2. read-after-write dependency,变量在一次循环迭代中被写入,在随后的迭代中被读取,无法被安全地向量化
for (j=1; j<MAX; j++)  A[j]=A[j-1]+1; 
  1. write-after-read dependency,变量在一次循环迭代中被读取,在随后地循环中被写入,可以安全地被向量化
for (j=1; j<MAX; j++)  A[j-1]=A[j]+1; 
  1. Read-after-read dependency,实际上都不构成数据依赖
  2. Write-after-write dependency,同一个变量在多次循环迭代中被写入,不能安全地被向量化

指示编译器向量化

  • pragma 预处理命令
  1. #pragma ivdep
    指示编译器忽略可能的 data dependency
  2. #pragma loop count (n)
    指示编译器循环次数的典型值,帮助其评估向量化的收益
  3. #pragma vector always
    指示编译器向量化循环
  4. #pragma vector align
    断言循环中的变量都是内存对齐的
  5. #pragma novector
    指示编译器不要向量化循环
  6. #pragma vector nontemporal
    提示编译器数据不会被再次使用,可以使用 stream store 来绕过 cache

注:这里所有的 pragma 指令都是针对 Intel 编译器 ICC 的,对于 GCC 可能会有不同。如 #pragma ivdep 在 GCC 中对应的使用方式是 #pragma GCC ivdep。

  • restrict 关键词
    restrict 关键词是C99标准引入的,用于指示编译器通过只能通过该指针访问指针指向的内存。类似于 #pragma ivdep,编译器可以假定通过指针访问的内存是互斥的,不存在交叠。

显式向量化

各种向量化的方式

有多种方式可以实现向量化,需要在易用性和程序的自定义程度上进行取舍。
相比于 Auto Vectorization,SIMD vectorization、SIMD intrinsic、Vector intrinsics 都是更为底层的向量化编程方式。SIMD vectorization 使用 #pragma omp simd 预处理指令命令编译器进行向量化。SIMD vectorization 在代码形式上与 Auto vectorization 非常类似。不同的是,前者是程序员显示命令编译器进行向量化,当无法向量化代码时编译器会返回错误导致编译失败;后者只作为一个hint,即使是 #pragma vector always 下,无法向量化代码时依然可以编译成功。

SIMD-enabled 函数

SIMD-enabled 函数通过对一个元素的标量操作,描述向量化的算法实现。在程序调用时,与普通函数一样操作单个元素。编译器实际上会编译生成针对多个元素的变体,采用 SIMD 指令并行处理。
可以通过 #pragma omp declare simd clauses
的预处理指令,声明一个 SIMD-enabled 函数。

// routine prototype
#pragma omp declare simd                           // universal but slowest definition matches the use in all three loops
#pragma omp declare simd linear(in1) linear(ref(in2)) uniform(mul) // matches the use in the first loop
#pragma omp declare simd linear(ref(in2))                            // matches the use in the second and the third loops
#pragma omp declare simd linear(ref(in2)) linear(mul)              // matches the use in the second loop
#pragma omp declare simd linear(val(in2:2))                          // matches the use in the third loop
extern int func(int* in1, int& in2, int mul);

参考文献

Vectorization (intel.com)
gcc Multiply.c Driver.c -lm -O3 -fopt-info-vec-all -o a2.out

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容