C++でrestrictと同様の機能を実現する方法

ループのベクトル化や、その他の最適化を促進させることを目的として、C言語のrestrict修飾子を使うことがよくある。

restrict修飾子を指定したポインタは、同じ関数の他のポインタが指すメモリアドレスを指さない。 複数のポインタが同じメモリアドレスを指すと、メモリ依存関係によって、ベクトル化ができなくなることが多い。 restrictを使うことで、コンパイラが「メモリ依存が無い」と判定できるようになって、ベクトル化が可能になることがある。

以下のようにするとベクトル化されない。 コンパイラはruntimeチェックを入れることで、restrictが無いプログラムもベクトル化してしまうので、-mllvm -pragma-vectorize-memory-check-threshold=0を指定している。

norestrict.c

void test(double *a, double *b, int len) {
  for (int i=1; i<len; i++) {
    a[i] *= b[i] + i;
  }
}
$ clang -O2 -S norestrict.c -fno-unroll-loops -Rpass=loop-vectorize -mavx512f -mllvm -pragma-vectorize-memory-check-threshold=0

以下のようにrestrictを付けるとベクトル化されることがメッセージからわかる。

restrict.c

void test(double *restrict a, double *restrict b, int len) {
  for (int i=1; i<len; i++) {
    a[i] *= b[i] + i;
  }
}
$ clang -O2 -S restrict.c -fno-unroll-loops -Rpass=loop-vectorize -mavx512f -mllvm -pragma-vectorize-memory-check-threshold=0
restrict.c:2:3: remark: vectorized loop (vectorization width: 8, interleaved count: 1) [-Rpass=loop-vectorize]
  for (int i=1; i<len; i++) {
  ^

一方、C++の場合は、以下のように構造体のメンバとすることでベクトル化できるようになる。 これは、「別のタグ名を持つ構造体への2つのポインタは別名ではない」というルールを使っている。

a.cpp

struct A {
  double v;
};

struct B {
  double v;
};

void test(struct A *as, struct B *bs, int len) {
  for (int i=1; i<len; i++) {
    as[i].v *= bs[i].v + i;
  }
}
$ clang++ -O2 -S a.cpp -fno-unroll-loops -Rpass=loop-vectorize -mavx512f -mllvm -pragma-vectorize-memory-check-threshold=0
a.cpp:10:3: remark: vectorized loop (vectorization width: 8, interleaved count: 1) [-Rpass=loop-vectorize]
  for (int i=1; i<len; i++) {
  ^

上は、-fno-strict-aliasingを指定すると、ベクトル化されなくなる。

$ clang++ -O2 -S a.cpp -fno-unroll-loops -Rpass=loop-vectorize -mavx512f -mllvm -pragma-vectorize-memory-check-threshold=0 -fno-strict-aliasing

また、C++では、コンパイラの独自拡張である__restrict____restrictが使える様子。

restrict.cpp

void test(double *__restrict__ a, double *__restrict__ b, int len) {
  for (int i=1; i<len; i++) {
    a[i] *= b[i] + i;
  }
}
 clang++ -O2 -S restrict.cpp -fno-unroll-loops -Rpass=loop-vectorize -mavx512f -mllvm -pragma-vectorize-memory-check-threshold=0 
restrict.cpp:2:3: remark: vectorized loop (vectorization width: 8, interleaved count: 1) [-Rpass=loop-vectorize]
  for (int i=1; i<len; i++) {
  ^

低レベルプログラミング | Igor Zhirkov, 吉川 邦夫, 吉川 邦夫 |本 | 通販 | Amazonのp.351 「14.6 厳密な別名のルール」を参考にした。