ハードウェアトランザクショナルメモリ(HTM)の仕組み
細粒度ロックの地獄を書かずに、複数アドレスの更新をまとめて原子的に行えます。HTMがキャッシュコヒーレンスで投機実行を追跡し、競合時に巻き戻す原理と、容量・割り込みでアボートする限界まで掴めます。
- 1.HTMはトランザクション中に触れた行を read set / write set としてキャッシュ上で追跡し、書き込みは投機的にキャッシュへ留め置く。コミット時に一括で可視化し、外部からの競合スヌープが検出されたら全体を巻き戻す(アボート)ことで原子性を実現する。
- 2.競合検出はコヒーレンスプロトコルに相乗りする。他コアからの無効化要求が自分の read/write set に当たれば衝突と判定してアボートし、ロールバックする。検出粒度はキャッシュライン単位なので、偽共有がそのままアボートを誘発する。
- 3.Intel TSXはHLE(既存ロックにプレフィックス、ロック省略)とRTM(xbegin/xend/xabortで明示)を提供するが、L1容量超過・割り込み・コンテキストスイッチ・一部命令で無条件アボートするため、HTMは高速パスにすぎず常に非投機のフォールバック経路が必須となる。
ロックを書かずに「まとめて原子的に」やりたい
複数のアドレスを矛盾なく更新したいとき、伝統的な手段は細粒度ロックです。だが正しい順序でロックを取り、デッドロックを避け、競合の少ない領域でも毎回ロックのコストを払う設計は難しく、しかも実際にはほとんど競合しない(楽観的に進めても衝突しない)ケースが多い。ハードウェアトランザクショナルメモリ(HTM)は、この「たいてい衝突しない」に賭けます。ロックを取らずにコードブロックを投機的に実行し、誰とも衝突しなければそのままコミット、衝突したら何もなかったかのように巻き戻す(アボート)。ブロック全体が、外から見て「全部起きた」か「何も起きなかった」かのどちらかにしかならない――これがトランザクションの**原子性(atomicity)**です。
鍵は、この原子性を新しい巨大なハードウェアではなく、既にあるキャッシュコヒーレンスプロトコル にほぼ相乗りして実現する点にあります。
read set / write set をキャッシュで追跡する
トランザクション中、CPU は触れた行に印をつけます。
- read set: トランザクション内で読んだキャッシュライン群。「この値が最後まで変わらない」ことを前提に投機を進めているので、外から書き換えられたら前提が崩れる。
- write set: 書いたライン群。これらの新しい値は投機的で、まだ他コアに見せてはいけない。
実装は通常、L1 キャッシュの各ラインにトランザクション用のビット(read-monitored / write-monitored)を足すだけです。read set のラインには「監視中」マークを、write set のラインには投機データを保持し、コミットまでメモリやL2へ書き戻さない(ライトバックを抑止する)。コミットが成功すれば、これら投機ビットを一括でクリアし、書いたラインは通常の Modified 行に昇格して初めて外部から見えるようになります。つまりコミットは論理的に一瞬で全write setが可視化される操作であり、これが原子性の「全部起きた」側を担います。
コヒーレンスは元々「他コアがこのラインに書こうとしたら自分に通知が来る」仕組みを持ちます。HTM はこの通知をそのまま「自分の前提が壊れたかの監視」に流用できます。だから read/write の集合監視に新しい配線をほとんど足さずに済む。逆に言えば、追跡できるのはキャッシュに載っている間だけで、ここから後述の容量制限が必然的に出てきます。
競合検出とアボート:コヒーレンスのスヌープに相乗りする
競合の判定もコヒーレンスに乗ります。他コアが自分のトランザクション中のラインを触りに来ると、無効化要求(Invalidate / RFO)や読み出し要求がスヌープとして届きます。HTM はこれを次のように解釈します。
- 外部の書き込みが自分の read set に当たる: 読んだ前提が変わる → 衝突。アボート。
- 外部の書き込みが自分の write set に当たる: 投機データと競合 → 衝突。アボート。
- 外部の読み出しが自分の write set に当たる: まだ見せてはいけない投機値を見られる → 衝突。アボート(実装によりどちらかをアボート)。
アボートが決まると、write set の投機行を破棄します。投機データはキャッシュ内にしか書いていないので、そのラインを無効化するだけで副作用がきれいに消える――メモリには一切漏れていないからロールバックが安価です。さらにトランザクション開始時に退避したレジスタ状態とプログラムカウンタを復元し、アボートハンドラへ制御を渡します。
競合判定はバイト単位ではなくキャッシュライン単位です。論理的に無関係な変数でも同一ラインに同居していれば、他コアのその変数への書き込みがスヌープとなって自分のトランザクションをアボートさせます。フォルスシェアリングが性能劣化だけでなく無駄なアボートとして顕在化するわけで、HTM 利用時はライン配置が成否を直接左右します。
Intel TSX:HLE と RTM、そして無条件アボート
Intel の HTM 実装が TSX(Transactional Synchronization Extensions) で、二つのインタフェースを持ちます。
| インタフェース | やり方 | 用途 | フォールバック |
|---|---|---|---|
| HLE | 既存ロックの取得/解放命令に XACQUIRE/XRELEASE プレフィックスを付ける | ソース無改変でロック省略を試す(後方互換: 旧CPUはプレフィックスを無視して普通にロック) | アボート時は本物のロックを取得して再実行(自動) |
| RTM | xbegin / xend で範囲を明示、xabort で自発アボート | 任意のクリティカルセクションを自前で組む | xbegin の戻り先(アボートハンドラ)を自分で書く(手動) |
HLEはロック変数への書き込みを投機化し、ロックを実際には取らずにクリティカルセクションへ入ります。複数スレッドが同じロックを「省略」したまま同時に走れ、互いのデータが衝突しなければ全員コミットできます。これが**ロック省略(lock elision)**です。RTMは xbegin でトランザクションを開始し、xbegin は成功時とアボート時で戻り値が変わるため、典型コードは次の形になります。
if (_xbegin() == _XBEGIN_STARTED) {
// 投機実行: ロックは取っていない
if (lock_is_taken(&lock)) _xabort(0xff); // 誰かが本物のロック取得中なら降りる
do_critical_work();
_xend(); // コミット
} else {
// アボート経路: 本物のロックを取って非投機で実行
take_lock(&lock);
do_critical_work();
release_lock(&lock);
}
決定的に重要なのは、HTM はいつでも理由を問わずアボートしうることです。コヒーレンス競合以外にも、次が無条件アボート要因になります。
- 容量超過: write set(実装上ほぼ L1 容量・連想度に縛られる)を超えると追跡しきれずアボート。read set は L2 まで使える実装もあるが、いずれにせよ有限。大きすぎるトランザクションは構造的に成功しない。
- 割り込み・例外・コンテキストスイッチ: タイマ割り込みやページフォルトが入るとアボート。OS は投機状態を保存しないので、長いトランザクションほど割り込みに当たって落ちやすい。
- 禁止命令:
cpuid、一部のシステム命令、I/O などはアボートを誘発する。 - ネスト深さ超過や実装依存の理由。
容量や割り込みは確率ではなく構造的に起きるため、「アボートが続いて永遠にコミットできない」入力が必ず存在します。よって HTM は単独で正しいプログラムを作れず、非投機のフォールバック経路(本物のロック等)が必須です。RTM ではこれを自分で書く責任があります。さらに投機経路は必ず「本物のロックが取られていないか」を read set に含めて確認しなければなりません。さもないと、フォールバックでロックを取って走っているスレッドと投機スレッドが同時にクリティカルセクションに入り、原子性が壊れます。
ロック省略との関係と限界
HTM の最大の実用価値はロック省略です。競合が低い場面では、ロックの実取得(書き込み所有権の往復、アトミック命令のコスト)を丸ごと回避し、複数スレッドが同一ロック下を真に並列に通過できます。粗粒度ロック1本のままで、細粒度ロック相当のスケーラビリティを「うまくいけば」得られる、という旨味です。
ただし限界もはっきりしています。
- 高競合では逆効果: 衝突が多いとアボート→フォールバック→再実行を繰り返し、投機の無駄仕事ぶんだけ純粋に遅くなる。アボート率を監視し、閾値を超えたらしばらく素直にロックを取る適応制御が要る。
- 進捗保証がない: HTM 単独ではフォワードプログレスを保証しないため、フォールバックがセーフティネットになる。
- サイズと時間の制約: 大きい/長いトランザクションは容量・割り込みで落ちる。クリティカルセクションは小さく短く保つのが鉄則。
- 可観測性の壁: アボートは投機状態を捨てるので、トランザクション内の
printf等は意味を持たない。デバッグはxbeginのアボートコードに頼る。
なお、トランザクション内で観測されるメモリ順序は外から見れば全コミットが原子的に効くため、個々の命令並べ替えより粗い粒度で扱えますが、フォールバック経路は通常どおりメモリ一貫性モデルに従う必要があります。HTM はあくまで「楽観的高速パス」であって、一貫性そのものを置き換えるものではありません。
「read/write set は L1 キャッシュ上で追跡し、書き込みはコミットまで投機的に留める」「競合検出はコヒーレンスのスヌープに相乗りし、検出粒度はライン(だから偽共有でアボート)」「原子性=コミットで全write set一括可視 or アボートで投機行破棄」「TSX は HLE(プレフィックス・自動フォールバック)と RTM(xbegin/xend/xabort・手動フォールバック)」「容量超過・割り込み・コンテキストスイッチで無条件アボート → 非投機フォールバックが正しさの要件」を押さえれば中核は固い。
まとめ
- HTM はロックを取らずコードブロックを投機実行し、衝突なければコミット、衝突すればアボートして巻き戻すことで原子性を提供する。
- read/write set をキャッシュ上のビットで追跡し、書き込みはコミットまで外部に見せない。競合検出は既存コヒーレンスのスヌープに相乗りし、判定粒度はキャッシュライン単位。
- Intel TSX は HLE(既存ロックのプレフィックス化によるロック省略)と RTM(xbegin/xend/xabort の明示制御)を提供する。
- 容量超過・割り込み・コンテキストスイッチ・禁止命令で無条件にアボートしうるため、HTM は高速パスにすぎず、非投機のフォールバック経路が正しさのために必須となる。
ロック自体のハード実装と HTM が回避したいコストはアトミック命令と同期プリミティブが、投機がアボートで巻き戻る発想の土台はアウトオブオーダー実行が掘り下げます。
CPU/メモリ/ディスク Article
ハードウェアトランザクショナルメモリ(HTM)の仕組みを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
HTM
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 5
導入後に効く点
競合検出はコヒーレンスプロトコルに相乗りする。他コアからの無効化要求が自分の read/write set に当たれば衝突と判定してアボートし、ロールバックする。検出粒度はキャッシュライン単位なので、偽共有がそのままアボートを誘発する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「HTM / トランザクショナルメモリ」に近いか確認する。
- 強みである「HTMはトランザクション中に触れた行を read set / write set としてキャッシュ上で追跡し、書き込みは投機的にキャッシュへ留め置く。コミット時に一括で可視化し、外部からの競合スヌープが検出されたら全体を巻き戻す(アボート)ことで原子性を実現する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。