非常に分かりやすく要点が整理されていて良い本だった。 特にアセンブリで書かれた実験用サンプルコード 1 があったのが嬉しかった。 概念として知ってはいても動かしてみると腑に落ちる感覚がある。 ここでは自分に宛てて雑多なメモを残しておく。

アセンブリ記法

AT&T記法よりもIntel記法の方がわかりやすい。これは自分も感じる。 Intel記法はdestination が左側にくる記法。 gdb向けに~/.gdbinit内でset disassembly-flavor intel とやっとくと良いかもね。

命令実行にかかるサイクル数の計測

add、mul、mov命令にかかるサイクル数を統計的に計測できる 2 。 大量に同一の命令を実行することで統計的に値を出していく。 さらにそれらの命令間で真のデータ依存を持たせることで、 スーパースカラやスーパーパイプラインの影響を排除し1命令ずつ実行していく。

分岐予測

分岐命令のメモリアドレス → ジャンプ先のメモリアドレスをテーブルにキャッシュしておく。 そして再度同じ分岐命令が発行された時にジャンプ先を予測するのが基礎的なアイデア。 関数からの復帰の場合にはreturn address専用のテーブルにキャッシュしておく。 条件を伴う分岐の場合には”100010”のようなビットマップの構造で過去数回分の結果を保持しておいてそれとマッチさせることで予測する。 あるいは対象の命令とメモリアドレスの観点で近くにある他の条件分岐の結果も考慮に入れる場合もある。 精度は95%程度、普通のCPUキャッシュヒット率は97〜%くらいなのでちょっと低め。

投機実行

条件分岐予測によってアウトオブオーダー実行することを投機実行と呼ぶ。 まあ本質的にはこれがアウトオブオーダー実行の一番の目的になる。 つまり、基本ブロック(条件分岐などで区切られた命令列)の領域を超えた命令のアウトオブオーダー実行できるため。

キャッシュコヒーレンシ

SMPにおけるキャッシュコヒーレンシはMSIプロトコルとその派生によって実現されている。 アイデアとしてはCPU0がメモリに書き込んだとき、同一アドレスを持つキャッシュライン(64バイトの集まり)が CPU0以外に登場すればそこにinvalidateフラグを立てるというもの。 つまりCPU0以外のCPUがそのメモリアドレスにアクセスする場合には必ずキャッシュミスが起こり、 それに伴い最新の値を主記憶から取得できるようにしている。

Memory consistency

特にマルチコアでメモリアクセスがある時に、メモリアクセスの順番が入れ替わることで意図しない結果を引き起こすので、それの対策としてMemory Consistencyを考えないといけない。 Memory consistencyとは、要するにアウトオブオーダー実行に制約を加えること。 とはいえインオーダー実装だとしてもメモリの仕組み(複数バンクとか?)によっては順序の入れ替えが発生しうるので注意。 x86だとTSOモデル?を元にしている。 x86ではstoreやload命令のメモリアクセス順は普通入れ替わらないが、異なるメモリ属性領域にある場合には入れ替えが起こる可能性がある。 x86 SSEではlfenceやsfence命令が入った。 Linuxのbarrier()マクロ、GCCのmemory clobber、C言語のvolatile修飾子はここでいうメモリ順序を保証しないことに注意する。 これは複雑な領域なので、プログラミング言語の提供するチャネルのような抽象化されたものを使った方が良い。

ordering_unexpected.S 3 でメモリの前段にあるストアバッファに起因するメモリアクセス順の入れ替わりを再現できる。

アトミック操作

キャッシュコヒーレンシプロトコルのMESIにおけるE(排他状態)を使うと実現できる。 LL/SC命令で囲った領域もアトミックな操作ができる。 実際にはSC命令の時にLL命令で読み出したメモリアドレスに書き込みがあれば失敗・なければ成功となる。 なので、LL/SC命令のブロックを成功するまで繰り返せば良い。 もちろん性能を確実に引き出すのは難しい。 単一のプロセッサであっても時分割されるとアトミック操作は必要。

現代的なCPU

現代的なCPUとしてRISC-VアーキテクチャのBOOM(The Berkeley Out-of-Order RISC-V Processor) 4 がある。