ロードストアキューとメモリ曖昧性除去
ロードを投機的に先行させて性能を稼ぎつつ正しさを壊さない仕組みが要点。ストアフォワーディング、メモリ依存予測、順序違反のリプレイまで内部動作で掴めます。
- 1.LSQはロード/ストアをプログラム順で追跡し、同一アドレスへの先行ストアからロードへ値を直接渡すストアフォワーディングで真のメモリ依存(RAW)を守る。
- 2.ストアのアドレスが未確定でもロードを投機的に先行発行し、依存予測器が「衝突しそうか」を学習して投機の可否を決める。
- 3.先行ロードが後から確定したストアと同一アドレスだったらメモリ順序違反。LSQが検出し、そのロード以降をフラッシュして再実行(リプレイ)する。
なぜメモリ命令だけ特別扱いが要るのか
アウトオブオーダ実行はレジスタ依存をリネーミングで解き、揃ったオペランドから順不同で実行します。ところがメモリ命令はレジスタと違い、どの番地を読み書きするかが実行してアドレスを計算するまで分からないという厄介さを持ちます。r1 と r2 が衝突するかはレジスタ番号を見れば即わかりますが、store [r3] と load [r4] が同じ番地かは、r3 と r4 の中身(実効アドレス)が確定するまで判定できません。
この「同じ番地かどうか分からない」状態を**メモリ曖昧性(memory ambiguity)と呼び、それを解いて順序を守る処理がメモリ曖昧性除去(memory disambiguation)です。担うのがロードストアキュー(LSQ)**で、ロードキュー(LQ)とストアキュー(SQ)に分かれます。
LSQが守る順序とストアフォワーディング
メモリ命令間にもレジスタと同じ依存があります。守るべきは真のRAW(先のストアが書いた値を後のロードが読む)だけで、WAR/WAWはストアバッファとリネームで吸収できます。LSQはロードとストアをそれぞれプログラム順で並べ、アドレスと値(ストアは未コミットのデータ)を保持します。
ロードが発行されると、LSQはそのロードより前にあるストアのうち同一アドレスのものを探します。見つかれば、そのストアの値をキャッシュやメモリを介さずロードへ直接渡します。これがストアフォワーディングです。ストアはコミットされるまでキャッシュに書けない(投機的かもしれない)ため、メモリを読んでも古い値しか得られません。SQ内の最新ストアから直接転送することで初めて正しい値が得られます。
ストアフォワーディングはアドレスとサイズが噛み合って初めて成立します。8バイトストアの中の4バイトを読むなら転送できますが、ストア範囲とロード範囲が「部分的にずれて重なる」と多くの実装は転送をあきらめ、ストアのコミットを待ってからキャッシュ経由で読み直します(store-to-load forwarding stall)。アラインメントを揃えることが性能に効くのはこのためです。
アドレスが未確定なら、どうするか
問題は、ロードを発行したいのに先行ストアのアドレスがまだ計算されていない場合です。とりうる方針は二つあります。
| 方式 | 未確定ストアがあるとき | 得失 |
|---|---|---|
| 保守的(順序待ち) | 全先行ストアのアドレス確定までロードを待たせる | 安全だが、衝突しない大多数のロードまで止まり並列度を失う |
| 投機的(依存予測) | 衝突しないと予測してロードを先行発行 | ほとんど当たり高速。外れたら検出してやり直す |
実際のメモリアクセスでは、未確定ストアと後続ロードが本当に同じ番地である割合はごく一部です。そこで現代の高性能コアは投機的に振る舞い、先行ストアのアドレスが未確定でもロードを先に走らせます。ただし「いつも投機」では衝突が多いコードで取り消しが頻発するため、メモリ依存予測器で投機の可否を学習します。
メモリ依存予測 ― 投機するかを学ぶ
メモリ依存予測器は「このロードは未確定の先行ストアと衝突しやすいか」を、過去の挙動から予測します。代表がStore Setsです。これは「過去に衝突したロードとストアの組」を同じ集合(store set)にまとめ、あるロード → それと過去に衝突したストア群 のように対応づけて記録します。あるロードに対し、その store set 内のストアがまだ未確定なら、そのロードは投機せずに待たせます。逆に対応するストアが無ければ自由に先行させます。
構図は分岐予測とそっくりです。先に進んで(投機)、後から正解(実アドレス)と照合し、外れたら巻き戻す。違いは予測対象が「分岐方向」ではなく「メモリが衝突するか否か」である点だけです。当たれば隠れた並列度を引き出し、外れてもやり直せるので正しさは保たれます。
順序違反の検出とリプレイ
投機が外れる、すなわちメモリ順序違反(memory order violation)は次の形で起きます。ロードを先行発行したが、その後で確定した先行ストアが実は同一アドレスだった――ロードは古い値を読んでしまっています。
検出はストア側で行います。ストアのアドレスが確定したとき、LSQはロードキューを後方検索し、そのストアより後ろのプログラム順で既に実行済みかつ同一アドレスのロードを探します。該当があれば、そのロードは読むべき値を取り逃した違反ロードです。
違反したロードは誤った値で後続の計算を進めている可能性があるため、値の差し替えだけでは不十分です。多くの実装は違反ロード以降の命令を一括フラッシュし、そのロードから再フェッチ・再実行します。これが**リプレイ(replay)**です。アウトオブオーダ実行のROBによる正確な例外と同じ巻き戻し機構を使い、違反ロードより前だけをコミット済みに保って再起動します。リプレイは分岐予測ミスに匹敵する重いペナルティで、依存予測の精度が性能を左右します。
順序違反の発生とリプレイ(時系列)
store [r3] ; r3 未確定のまま発行待ち
...
load [r4] ; 投機的に先行実行 → 古い値を取得(誤り)
...
(後で) r3 確定 → [r3] == [r4] と判明
↓ ストアがLQを後方検索し違反ロードを発見
load 以降をフラッシュ → load から再実行(リプレイ)
なぜキャッシュの裏でこれが効くのか
LSQはコミット前のストアを溜める緩衝でもあります。ストアはキャッシュの原理に従いコミット時に初めてキャッシュへ書き戻され、それまではSQ内に滞留します。だからこそ後続ロードはSQから値を得る必要があり、ストアフォワーディングが不可欠になります。さらにロードを投機先行させることで、長レイテンシのキャッシュミスを後続命令の陰に隠せます。パイプラインのハザードで見た「依存待ちの停滞」を、メモリ領域でも投機で乗り越えるのがLSQの役目です。
「LSQはメモリ命令の順序を追跡し、同一アドレスの先行ストアからロードへ値を渡すのがストアフォワーディング」「アドレス未確定時はメモリ依存予測(Store Sets等)でロードを投機先行させる」「先行ロードが後続確定ストアと同一アドレスだとメモリ順序違反で、検出してリプレイ(フラッシュ&再実行)する」の3点が頻出です。守るべきはRAWだけ、という点も押さえましょう。
まとめ
- メモリ命令はアドレスが実行まで未確定なため、レジスタと別にLSQが順序を追跡してメモリ曖昧性を除去する。
- 同一アドレスの先行ストアからロードへ直接値を渡すストアフォワーディングでRAWを守る。アドレス/サイズが噛み合わないと転送は失敗しストール要因になる。
- アドレス未確定時はメモリ依存予測でロードを投機先行させ、隠れた並列度を引き出す。外れにくいよう過去の衝突を学習する。
- 先行ロードが確定ストアと衝突するメモリ順序違反はストアがLQを後方検索して検出し、違反ロード以降をリプレイして正しさを回復する。
CPU/メモリ/ディスク Article
ロードストアキューとメモリ曖昧性除去を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ロードストアキュー
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 5
導入後に効く点
ストアのアドレスが未確定でもロードを投機的に先行発行し、依存予測器が「衝突しそうか」を学習して投機の可否を決める。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「ロードストアキュー / メモリ曖昧性除去」に近いか確認する。
- 強みである「LSQはロード/ストアをプログラム順で追跡し、同一アドレスへの先行ストアからロードへ値を直接渡すストアフォワーディングで真のメモリ依存(RAW)を守る。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。