コンパイラが自動ベクトル化を拒んだ理由は浮動小数点の丸め誤差だった

Trying to Make a Loop Auto-Vectorize

Rustで書いた単純な内積計算が自動ベクトル化されないのはなぜか。コンパイラはIEEE 754の非結合性のため浮動小数点演算の順序を勝手に変えられず、手動で4要素ずつに分割するとベクトル化に成功する。整数版は問題なくベクトル化される。

コンパイラは浮動小数点演算の順序を安全に並べ替えることができない。なぜならIEEE 754の浮動小数点演算は結合的ではなく、つまり (a + b) + c は必ずしも a + (b + c) と等しくならないからだ。
  1. dzdt

    Matt Pharr(SIMDプログラミング言語ISPCの作者)は「自動ベクトル化はプログラミングモデルではない」という洞察で核心を突いた。

    Mattはこう書いている([1]):

    「自動ベクトル化器の問題は、ベクトル化が失敗し得る限り(そして実際失敗する)、コンパイラが自分のプログラムに対して生成するコードを気にするプログラマなら、自動ベクトル化器を深く理解しなければならなくなることだ。そして、ベクトル化したいコードのベクトル化に失敗したとき、正しい方法でつつくか、プログラムを正しい方法で変更して再びうまくいくようにしなければならない。これはひどいプログラミングのやり方だ。すべて錬金術と当て推量であり、単一のコンパイラ実装のニュアンスについて深く専門化する必要がある——本来ならまったく気にする必要のないことだ。」

    [1] https://pharr.org/matt/blog/2018/04/18/ispc-origins

  2. gnufx

    もちろん、ベクトル化はSIMDを意味しない。最初のベクトル化コンパイラはSIMDよりずっと前のCDC(?)システム向けだった。今日では例えばArmのSVEがあり、SIMDのNeonとは別物だ。

    とにかく、私はRustよりもC(とFortran)で数値ループを最適化することに慣れている。同じセマンティクス(還元における数値的等価性など)でGCCの自動ベクトル化ができなかったところを、単にSIMD intrinsicsを使うことでうまくいった例はほとんど見たことがない。もちろんほとんどの場合、-fassociative-mathで済ませられ、ピーク性能を犠牲にしない。例えばBLISはそれを有効にしたまま広範なテストに合格しているが、もちろん確認すべきだ。(GCCはまた、Arm(Neon?)をそもそもベクトル化させるためにこのオプションが必要だと文書化している。)ほとんどの場合、人々がItelコンパイラがいかに優れているかを語るとき、それは誤って-funsafe-mathのようなものをデフォルトにしているからだ。

    いずれにせよ、GCC(他のコンパイラと同様)はアセンブラを見なくても-fopt-info-オプションでベクトル化について教えてくれるし、驚くこともある。例えば、正だと分かっているループインデックスにCでunsignedを使うと、Cのオーバーフローセマンティクスのせいで「loop not affine」によるベクトル化失敗を見ることになる。代わりにsigned型を使え。

    適切に最適化された数値ライブラリ(通常はBLAS)を使うもう一つの理由は、少なくともレベル3(行列-行列)演算の場合だ。メモリ階層に対してブロッキングを正しく行っても、ベクトル化だけでは通常ピーク性能は得られない。なぜならtri […]

この日のほかの記事

2026-09-14