Intertangle の形成方法
2つのサブシステムは、最初は独立したモジュールとして始まる。時間が経つにつれ、それぞれが共有のゴッドオブジェクト(グローバル設定構造体、シングルトンマネージャー、静的クラス)にフィールドを蓄積する。各追加は個別には正しいが、結合は小規模なテストでは見えない。
これが硬直化した3つの基盤:
VLC メディアプレイヤー。 オーディオ、ビデオ、プレイリストは、グローバルプレイヤー状態を守る単一のロックを共有する。タイムスタンプへのスキップ要求はロックを取得し、再生位置を変更し、オーディオバッファをフラッシュする。同じロックを待つビデオサブシステムは停止する。プレイリストサブシステムも待機し、プリフェッチできない。結果: 3つの独立したサブシステムが単一の状態オブジェクトを通じて直列化される。パフォーマンスコスト: N = サブシステム数の O(N) ロック競合で、すべて操作レイテンシに比例する。
Redis イベントループ。 AOF fsync(ディスク書き込み)、レプリケーション(ネットワーク書き込み)、コマンド実行(CPU)は、すべてシングルスレッドのイベントループを共有します。それぞれは単独では正しく動作しますが、低速な fsync がコマンド実行を停滞させます。書き込み負荷下ではレプリケーション遅延がさらに悪化します。結合点は、異なるレイテンシ特性を持つ操作が単一の実行コンテキストを共有していることです。
LevelDB VersionSet。 書き込みパス(memtable フラッシュ)とバックグラウンドコンパクションは VersionSet ロックを共有します。コンパクションジョブはロックを数十ミリ秒保持するため、書き込みパスが停滞します。どちらの操作も必要ですが、結合はタイミングではなく構造的なものです。
重要な区別
Intertangle は構造的な結合であり、タイミングの問題ではありません。競合状態とは、2 つのスレッドが同期なしに共有状態にアクセスすることです。修正方法はミューテックスを追加することです。
Intertangle では、2 つのサブシステムが設計上状態を共有しています。ミューテックスを追加しても結合は解消されず、アクセスが直列化されるだけです。サブシステムは依然として状態を共有しており、ボトルネックはさらに厳しくなります。
VLC の Intertangle にミューテックスを追加すると悪化します。オーディオ、ビデオ、プレイリストがすべて単一のロックを待つことになるからです。構造的な修正は、各サブシステムに独自の状態を持たせることです。フェーズスナップショット:フェーズ境界で共有状態のスナップショットを凍結し、各サブシステムが独立してスナップショットを読み取り、最後に書き込みをマージします。
構造的 vs タイミング
Intertangle を診断する重要な質問:ミューテックスを追加すると問題は解決するのか、それとも悪化するのか?
競合状態:ミューテックスを追加することで修正される。正しいアクセス順序により破壊が解消される。
インタータングル:ミューテックスを追加することでアクセスは直列化されるが、構造的な結合は残る。サブシステムは依然として状態を共有する。負荷がかかると互いにブロックし合う。ボトルネックは狭まる。
インタータングルを見つける方法
3つの検出シグナル:
1. サブシステム間で共有される可変フィールド。 複数のサブシステムが読み書きするフィールドを持つgod-object。あるサブシステムのフィールドアクセスを削除したときに別のサブシステムが壊れる場合、それらは状態を共有しています。
2. 無関係な操作を保護する単一のミューテックス。 1つのロックがオーディオフラッシュ、ビデオデコード、プレイリスト取得のすべてを保護している:レイテンシプロファイルが異なる3つのサブシステムが互いに待機している。臭い:同じロック名の下に無関係な操作がある。
3. 負荷追加時のパフォーマンス回帰。 操作AとBが独立しているように見えても、操作Bが同時に実行されると操作Aのレイテンシが増加する。それらは独立しておらず、状態を共有している。
Phase-Snapshot修正
Phase snapshotパターン:
# BEFORE: サブシステムが共有状態を直接読み書きする
class GameWorld:
position = {} # 共有のミュータブル
velocity = {} # 共有のミュータブル
def physics_tick(world):
for entity in world.entities:
world.position[entity] += world.velocity[entity] # ループ途中で共有状態を書き換え
# AFTER: フェーズ前のスナップショットが凍結される。以降の書き込みは next_state バッファへ
def physics_tick(world):
snapshot = world.freeze() # 不変ビュー
next_state = {}
for entity in snapshot.entities:
next_state[entity] = snapshot.position[entity] + snapshot.velocity[entity]
world.merge(next_state) # フェーズ境界でアトミックにマージ
すべてのサブシステムがスナップショットを読み取ります。どのサブシステムもスナップショットへ書き込みません。書き込みはバッファに蓄積され、フェーズ境界でアトミックにマージされます。サブシステムは独立して実行されます:ロック競合なし、順序依存なし、隠れた結合なし。
修正を適用する
あるチームが欠陥を報告しました。ゲームエンジンのアニメーションシステムと衝突システムの両方が、共有のエンティティ変換オブジェクトに書き込んでいます。両方が同じティックで実行されると、衝突の結果はアニメーションが先に実行されたかどうかに依存します。ミューテックスを追加することで順序は固定されましたが、アニメーションは衝突がブロードフェーズスイープを実行するたびに停止するようになりました。