常见的向量化技巧

面向英特尔® MIC 架构的编译器方法

本文收集了关于向量化的各种技巧:

在向量循环中处理用户定义的函数调用

如果您希望对具有用户定义的函数调用的循环进行向量化处理,(可能要重建代码)请将函数调用作为向量基本函数。

在基本函数中指定单位步长访问

如果您的基本函数访问单位步长中的内存,您可以通过两种方式来编写:

  • 线性整数对统一指针进行索引

  • 线性指针

__declspec(vector(uniform(a),linear(i:1)))
float foo(float *a, int i){
  return a[i]++;
}

__declspec(vector(linear(a:1)))
float foo1(float *a){
  return (*a)++;
}

处理向量循环内部的内存消歧

针对简单的循环考虑向量化:

void vectorize(float *a, float *b, float *c, float *d, int n) {
    int i;
    for (i=0; i<n; i++) {
        a[i] = c[i] * d[i];
        b[i] = a[i] + c[i] - d[i];
    }
} 
  • 此处,编译器不知道这 4 个指针指向何处。作为一名编程人员,你可能知道它们指向完全独立的位置。编译器不会这么认为。除非编程人员明确地告诉编译器它们指向独立的位置,否则编译器必须假定它们彼此使用 VERY BADLY 别名 --- 例如,c[1] 和 a[0] 可能位于相同的地址,因此根本不能对循环进行向量化处理。

  • 当未知指针的数量非常少时,编译器可以生成一个运行时检查,以及循环的优化和非优化版本(编译时间、代码大小以及运行时测试中存在开销)。由于开销会快速增长,因此 "VERY SMALL" 数值必须非常小 ---- 例如 2 --- 即便如此,由于没有告诉编译器“指针是独立的”,您仍会受到一定的影响。

  • 因此,更好的方法是告诉编译器“指针是独立的”。其中一个方法是使用 C99 "restrict pointer" 关键字。即便你没有使用 "C99 标准" 进行编译,您可以使用 -restrict (Linux) 和 -Qrestrict (Windows) 标记,以便使英特尔编译器接受 "restrict" 关键字。

void vectorize(float *restrict a, float *restrict b, float *c, float *d, int n) {
    int i;
    for (i=0; i<n; i++) {
        a[i] = c[i] * d[i];
        b[i] = a[i] + c[i] - d[i];
    }
}
  • 此处,编程人员应告诉编译器 "a" 和 "b" 没有其它别名。

  • 另外一种方法是使用 IVDEP 编译指示。IVDEP 的语义与限制指针不同,但是它可以使编译器消除某些假定的依赖性,这足以使编译器认为向量化是安全的。

void vectorize(float *a, float *b, float *c, float *d, int n) {
    int i;
#pragma ivdep
    for (i=0; i<n; i++) {
        a[i] = c[i] * d[i];
        b[i] = a[i] + c[i] - d[i];
    }
}

避免 64 位整数与浮点之间的转换

  • 避免 64 位整数与浮点之间的转换。

  • 请使用 32 位整数。

  • 如果可能,请使用 32 位的带符号整数(最高效)

避免 64 位整数对收集/分散进行索引

  • 64 位索引收集/分散没有高效的硬件支持

带有间接访问的循环的向量化

考虑带有间接内存加载/存储的循环的向量化,如下所示:

 for (i = kstart; i < kend; ++i) {
    istart = iend;
    iend   = mp_dofStart[i+1];
    float w = xd[i];

    for (j = istart; j < iend; ++j) {
        index  = SCS[j];
        xd[index] -= lower[j]*w;
    }
 }

对于上面所示的代码来说,向量化的关键条件是 xd 值是不同的(否则便会存在真正的依赖性,从而不能对循环进行向量化处理。如果是这种情况,唯一的方法便是以向量友好型的方式重新编写算法)。如果 xd 值保证(由用户)是不同的,那么便可以使用 ivdep/simd 编译指示(在内部 j-loop 之前)执行向量化操作。编译器仍将生成收集/分散向量化 — 如果存在可以使用其单位步长的算法列示,则会达到更好的效果。

有关编译器优化的更完整信息,请参阅优化通知