|
> とあるのですが、私はアセンブラ言語の知識が少々ありますので勝手に最適化されたら
> 困る場所は、アセンブラでコードを書けば順序を保証できるのでしょうか?
> よろしくお願いします。
メモリフェンスを適切に使わなければ、ゴリゴリアセンブラで書いても
CPU が勝手に行う並べ替えを防げませんので不十分です。
例えば、以下のふたつのスレッドの処理を考えてみます。
各 mov は atomic なので、ふたつの mov が別々の CPU で真に同時実行
されたとしても、 mov がメモリを操作している途中に他の mov が
割り込んで半端な結果になってしまう、といったことはなく、必ず単純に
順序付けて考えることができます。
※ [x], [y] の初期値は共に 0 とする。
スレッド1
1: mov [x], 1
2: mov [y], 1
スレッド2
3: mov eax, [y]
4: mov ecx, [x]
上記のふたつのスレッドが異なるコアでほぼ同時に実
行された場合、各命令の実行順と、その結果として得られる
eax, ecxの内容は、メモリオーダリングを考慮しなければ、
以下のいずれかになるはずです。
実行順 eax ecx
1, 2, 3, 4 1 1
1, 3, 2, 4 0 1
1, 3, 4, 2 0 1
3, 1, 2, 4 0 1
3, 1, 4, 2 0 1
3, 4, 1, 2 0 0
この結果からは、 eax = 1, ecx = 0 となるパターンはありません。
しかし、現実には、
1 と 2 が逆転して、 2, 3, 4, 1 の順で実行される
3 と 4 が逆転して、 1, 4, 3, 2 の順で実行される
などといったことが発生し、eax = 1, ecx = 0 となることもあります。
# 細かいことを言えば、現行の x86 CPU (x64含む) の中には、
# 1と2が逆転する (異なるアドレスに対する書き込み順が入れ替わる)
# ものはなかったと思うけど。あくまで「今は」であって、将来にわたて
# 保証されるものではありませんが。
でも、それじゃ困る場面が出てくることがあります。そのため、
順序保証をするためにメモリバリアという命令があるわけです。
今回の場合、以下のようにすると、1→2、3→4の順序が保証されるので、
eax = 1, ecx = 0 となるパターンがなくなります。
スレッド1
1: mov [x], 1
mfence
2: mov [y], 1
スレッド2
3: mov eax, [y]
mfence
4: mov ecx, [x]
※ メモリフェンス命令は mfence だけでなく、lfence や sfence
というものもあります。あと lock プレフィクス使う手も。詳細は割愛。
# メモリアクセスの命令だけじゃなくて、他の命令も、いくつかの命令を
# まとめて実行したり (スーパースカラー)
# 順序を入れかえて実行したり (アウトオブオーダー)
# してるんだよね。
# 他の命令はその CPU 内部で完結するから実害ないだけで。
で、アセンブリで書かないとメモリフェンスを狙った位置に入れることは
できないのか、といえばそうではなくて、VC2005以降の volatile で
限定的とはいえ保証されていたり (このスレで既出)、その他、
処理系固有の拡張でメモリフェンスを適切に挿入できるようになっていたりしています。
C11、C++11以降だと、メモリモデルやマルチスレッドについての規定も
加わったので、標準の範囲で対策できるようにもなってきたり。
|