volatile爆発しろ!
volatileという英単語には「爆発しやすい」という意味があるようですね。豆知識。
さて、前エントリのスライドで説明したように、マルチスレッドプログラムを正しく動作させるためにはCPUやコンパイラによるリオーダーを制限することが重要であり、そのためにメモリバリア(メモリフェンス)という仕組みがあるわけです。そして、CやC++のvolatile変数はメモリバリアとしての効果を持っていない、という問題点について指摘しました。この部分をもう少し掘り下げてみます。
マルチスレッドプログラムを正しく同期化させるために必要な「メモリバリア付きアトミック操作」について、それが正しく動作するために満たすべきチェックポイントを挙げると以下になります。
- コンパイラは、その操作自体を最適化によって消去してしまったりしない。
- コンパイラは、その操作をアトミックに実行できる命令を出力する。
- コンパイラは、その操作をまたいだリオーダーを行わない。(メモリバリアで指定された方向について)
- CPUは、コンパイラの出力するアトミック命令をまたいだリオーダーを行わない。(メモリバリアで指定された方向について)
- (4が×の場合)コンパイラは、CPUのリオーダーを抑制するためのメモリバリア命令を一緒に出力する。
この 1, 2, 3, 4 または 1, 2, 3, 5 が○でないと「メモリバリア付きアトミック操作」としては欠陥があることになりますが、実際にvolatile変数へのloadやstoreについて調べてみると、以下のような結果となります。*1
gcc(x86) aligned volatile int 1.○ 2.○ 3.× 4.○ 5.− gcc(x86) aligned volatile long long 1.○ 2.× 3.× 4.− 5.− gcc(PowerPC) aligned volatile int 1.○ 2.○ 3.× 4.× 5.× VC++2005より前(x86) aligned volatile int 1.○ 2.○ 3.× 4.○ 5.− VC++2005以降(x86) aligned volatile int 1.○ 2.○ 3.○ 4.○ 5.− VC++2005以降(Xbox 360/PowerPC) aligned volatile int 1.○ 2.○ 3.○ 4.× 5.×
ご覧の通り、「VC++2005以降(x86)」以外は全滅です。だからといって「volatile爆発しろ!」なんて叫んではいけません。そもそも C/C++ の仕様通りの動作なわけですから。
ちなみに、先ほどのVC++の結果については以下の解説文書を参考にしました。
Xbox 360 と Microsoft Windows でのロックレス プログラミングの考慮事項
この文書によると、アトミック操作として一般的に知られているInterlocked*命令も、PowerPCアーキテクチャ(Xbox 360)ではメモリバリア効果が不十分なことがあるので注意しなければならないようです。先ほどと同じチェックポイントで確認すると以下のようになります。
VC++(x86) InterlockedIncrement 1.○ 2.○ 3.○ 4.○ 5.− VC++(Xbox 360/PowerPC) InterlockedIncrement 1.○ 2.○ 3.○ 4.× 5.× VC++(Xbox 360/PowerPC) InterlockedIncrementAcquire/InterlockedIncrementRelease 1.○ 2.○ 3.○ 4.× 5.○
このように、PowerPCなどの弱いメモリモデルを採用しているCPUでは、メモリバリアの有無についても特に注意しなければなりません。
続き
*1:loadについてはacquireバリアの、storeについてはreleaseバリアの有無について調べています。