Skip to content
Go back

Cold, Hard Cache: Insomniac Games' Cache Simulator

· Updated:

url

拙訳

Cold, Hard Cache - Insomniac Games’ Cache Simulator

Andreasと申します

  • Insomniacでコアツール&基盤チームを率いている

Andreasと申します

  • Insomniacでコアツール&基盤チームを率いている
  • 今日のトピック: Insomniac GamesのCacheSim
    • キャッシュ効率を測定するための独自のツール群

Andreasと申します

  • Insomniacでコアツール&基盤チームを率いている
  • 今日のトピック: Insomniac GamesのCacheSim
    • キャッシュ効率を測定するための独自のツール群
    • 本日、これをオープンソース化して、あなた方と共有しようとすることに興奮している

目で解るキャッシュとメモリのサイズ

目で解るキャッシュとメモリのサイズ

目で解るキャッシュとメモリのサイズ

目で解るキャッシュとメモリのサイズ

目で解るキャッシュとメモリのサイズ

サイクルでのキャッシュおよびメモリのアクセス速度

背景

  • キャッシュはRAMより何倍も高速で小さい
    • そこに入れるものは大きく異なる

背景

  • キャッシュはRAMより何倍も高速で小さい
    • そこに入れるものは大きく異なる
  • メモリ操作はプログラムに追加するのが極めて簡単である
    • コストは隠蔽され、不明確である

背景

  • キャッシュはRAMより何倍も高速で小さい
    • そこに入れるものは大きく異なる
  • メモリ操作はプログラムに追加するのが極めて簡単である
    • コストは隠蔽され、不明確である
  • アクセスパターンで使えるデータを是非とも必要としている
    • パフォーマンスを意識したプログラマーには選択肢がそれほど豊富ではない

サンプリングプロファイラ

  • 基本的には上手くいった --- ほとんどのプロファイラはサンプルベースである
    • 多くのワークフローに対して優秀である
    • キャッシュに関するハードウェアの状態を収集するためにCPUの機能を活用する

サンプリングプロファイラ

  • 基本的には上手くいった --- ほとんどのプロファイラはサンプルベースである
    • 多くのワークフローに対して優秀である
    • キャッシュに関するハードウェアの状態を収集するためにCPUの機能を活用する
  • 制限: N個の命令に1回だけサンプルされる(Nは大きい)

サンプリングプロファイラ

  • 基本的には上手くいった --- ほとんどのプロファイラはサンプルベースである
    • 多くのワークフローに対して優秀である
    • キャッシュに関するハードウェアの状態を収集するためにCPUの機能を活用する
  • 制限: N個の命令に1回だけサンプルされる(Nは大きい)
  • より小さく、“バースト性の高いbursty1”ワークロードには理想的ではない
    • 統計学的には、ものが小さければ小さいほど再現可能性が低下すると言える
    • 正しい方向を指し示すが、ズバリではない

サンプリング空間の外側

  • cachegrind --- valgrindの一部
    • プログラムの命令ストリームに基づいてキャッシュをシミュレートする

サンプリング空間の外側

  • cachegrind --- valgrindの一部
    • プログラムの命令ストリームに基づいてキャッシュをシミュレートする
  • 利点:
    • 極めて徹底的 --- すべてのメモリアクセスがシミュレートされる

サンプリング空間の外側

  • cachegrind --- valgrindの一部
    • プログラムの命令ストリームに基づいてキャッシュをシミュレートする
  • 利点:
    • 極めて徹底的 --- すべてのメモリアクセスがシミュレートされる
  • 欠点:
    • Linuxのみ
    • 二者択一
    • 興味のある所までたどり着くのが極めて遅い

サンプリング空間の外側

  • cachegrind --- valgrindの一部
    • プログラムの命令ストリームに基づいてキャッシュをシミュレートする
  • 利点:
    • 極めて徹底的 --- すべてのメモリアクセスがシミュレートされる
  • 欠点:
    • Linuxのみ
    • 二者択一
    • 興味のある所までたどり着くのが極めて遅い
  • ちなみに、前世代にあったベンダ固有のツールは素晴らしかった
    • 短時間で要求に応じてシミュレートされたキャプチャを行うことができた

キャッシュシミュレーションツール群が欲しい理由

void PushBuffer::SetTextureAssets(uint32_t start_slot, uint32_t slot_count,
                                  const TextureAsset** textures_assets,
                                  uint32_t slot_mask, uint32_t hq_mask) {
  // ...
  for (uint32_t itex = 0; itex < slot_count; ++itex, tex_unit_test <<= 1) {
    Texture const* tex = (slot_mask & tex_unit_test)
                             ? textures_assets[itex]->GetTexture()
                             : NULL;
    // ...
    if (tex != NULL) {
      view = (textures_assets[itex]->GetFormatFlags() &
              TextureFormatFlags::kIsCube)
                 ? tex->m_View
                 : tex->m_ViewArray;
      samp = (hq_mask & tex_unit_test)
                 ? textures_assets[itex]->GetAnisoSampler()
                 : tex->m_SamplerState;
    }
    // ..
  }
  // ..
}

キャッシュシミュレーションツール群が欲しい理由

1フレームで2800回のL2キャッシュミス

小さきことなり

  • メンバはキャッシュラインを移動していた
    • 16ビットのFormatFlagsフィールドへのアクセスはそのときは約束されたL2キャッシュミスであった

小さきことなり

  • メンバはキャッシュラインを移動していた
    • 16ビットのFormatFlagsフィールドへのアクセスはそのときは約束されたL2キャッシュミスであった

小さきことなり

  • メンバはキャッシュラインを移動していた
    • 16ビットのFormatFlagsフィールドへのアクセスはそのときは約束されたL2キャッシュミスであった
  • 修正: ヘッダにて2つの行を文字通り入れ替える

小さきことなり

  • メンバはキャッシュラインを移動していた
    • 16ビットのFormatFlagsフィールドへのアクセスはそのときは約束されたL2キャッシュミスであった
  • 修正: ヘッダにて2つの行を文字通り入れ替える
  • ビュー次第では150〜250usの節約となる

小さきことなり

  • メンバはキャッシュラインを移動していた
    • 16ビットのFormatFlagsフィールドへのアクセスはそのときは約束されたL2キャッシュミスであった
  • 修正: ヘッダにて2つの行を文字通り入れ替える
  • ビュー次第では150〜250usの節約となる
  • かけた時間time investment: 30分

キャッシュシミュレーションツール群が欲しい更なる理由

  • 小さなユーティリティが時折大量にキャッシュミスを引き起こす
    • Vec3 SceneObject::GetPosition() { return m_ObjToWorld.v《3》; }

キャッシュシミュレーションツール群が欲しい更なる理由

  • 小さなユーティリティが時折大量にキャッシュミスを引き起こす
    • Vec3 SceneObject::GetPosition() { return m_ObjToWorld.v《3》; }
  • 単一のreturn文を最適化できない! (ノಠ益ಠ)ノ彡┻━┻
    • でも、less naiveとなるためにその関数の呼び出し元を最適化できる

キャッシュシミュレーションツール群が欲しい更なる理由

  • 小さなユーティリティが時折大量にキャッシュミスを引き起こす
    • Vec3 SceneObject::GetPosition() { return m_ObjToWorld.v《3》; }
  • 単一のreturn文を最適化できない! (ノಠ益ಠ)ノ彡┻━┻
    • でも、less naiveとなるためにその関数の呼び出し元を最適化できる
  • 同様にコールスタックを追跡する必要があり、passはスタックのせいにする

キャッシュシミュレーションツール群が欲しい更なる理由

  • 小さなユーティリティが時折大量にキャッシュミスを引き起こす
    • Vec3 SceneObject::GetPosition() { return m_ObjToWorld.v《3》; }
  • 単一のreturn文を最適化できない! (ノಠ益ಠ)ノ彡┻━┻
    • でも、less naiveとなるためにその関数の呼び出し元を最適化できる
  • 同様にコールスタックを追跡する必要があり、passはスタックのせいにする
  • ひとつのゲームプレイシステムに起因するGetPosition()における14kのキャッシュミスのうち12k個を見つけた 😊

コールドデータとしてSceneObject::GetPosition()を呼び出しているのは誰?

uint32_t num_groups = 0;
// Get a ton of stuff to work on
ComponentHandle* groups = g_PlacedPedestrianSystem.GetGroups(num_groups);
for (uint32_t idx = 0; idx < num_groups; ++idx) {
  PlacedPedestrianGroup* group = (PlacedPedestrianGroup*)groups《idx》.Resolve();
  if (!group) continue;
  const Vec3 &group_pos = group->GetActorPosition();
  // ...
}

コールドデータとしてSceneObject::GetPosition()を呼び出しているのは誰?

1フレームで約12000回のL2キャッシュミス

すでに知っていることについて尋ねたりはしない

  • PCでは1フレームあたり約1ms
    • フレームごとに処理される幾千もの(順不同な)項目

すでに知っていることについて尋ねたりはしない

  • PCでは1フレームあたり約1ms
    • フレームごとに処理される幾千もの(順不同な)項目
  • 調査: これらは動かない

すでに知っていることについて尋ねたりはしない

  • PCでは1フレームあたり約1ms
    • フレームごとに処理される幾千もの(順不同な)項目
  • 調査: これらは動かない
  • 修正: 並列な配列に位置のローカルキャッシュを保持する
    • 完全にGetPosition()の呼び出しを回避する

すでに知っていることについて尋ねたりはしない

  • PCでは1フレームあたり約1ms
    • フレームごとに処理される幾千もの(順不同な)項目
  • 調査: これらは動かない
  • 修正: 並列な配列に位置のローカルキャッシュを保持する
    • 完全にGetPosition()の呼び出しを回避する
  • 結果: フレーム当たり650usの節約

すでに知っていることについて尋ねたりはしない

  • PCでは1フレームあたり約1ms
    • フレームごとに処理される幾千もの(順不同な)項目
  • 調査: これらは動かない
  • 修正: 並列な配列に位置のローカルキャッシュを保持する
    • 完全にGetPosition()の呼び出しを回避する
  • 結果: フレーム当たり650usの節約
  • かけた時間: 2時間

命令、命令

  • 我々の欲しいデータすべてはすぐそこ、命令ストリームにある
    • 我々はそれが実行するかのように各命令を調べる方法が欲しい”だけ”である

命令、命令

  • 我々の欲しいデータすべてはすぐそこ、命令ストリームにある
    • 我々はそれが実行するかのように各命令を調べる方法が欲しい”だけ”である
  • 計画:
    • 興味のある地点に到達するときにスイッチを切り替える
    • すべての命令を追跡する(何とかして)
    • メモリアクセスごとにシミュレートされたキャッシュを更新する
    • (フレーム終わりになったら)追跡を切って、報告する!

命令、命令

  • 我々の欲しいデータすべてはすぐそこ、命令ストリームにある
    • 我々はそれが実行するかのように各命令を調べる方法が欲しい”だけ”である
  • 計画:
    • 興味のある地点に到達するときにスイッチを切り替える
    • すべての命令を追跡する(何とかして)
    • メモリアクセスごとにシミュレートされたキャッシュを更新する
    • (フレーム終わりになったら)追跡を切って、報告する!
  • でも、どうやって?

どこから始めよう?

  • 初めに: 理解のある上司を持つことは本当に役立つ

どこから始めよう?

  • 初めに: 理解のある上司を持つことは本当に役立つ

どこから始めよう?

  • 初めに: 理解のある上司を持つことは本当に役立つ
  • 調査のための2週間のプロジェクトを売り込んだ
    • 表題: “I can see crazy town from here”

どこから始めよう?

  • 初めに: 理解のある上司を持つことは本当に役立つ
  • 調査のための2週間のプロジェクトを売り込んだ
    • 表題: “ここから修羅の国を理解できる”
  • 承認!
    • では、修羅の国の外側にテントを立てることから始めよう

命令に関する推理

  • バイナリ計測フレームワークは存在する(既製品が)
    • DynamoRIO、Intel PIN、他

命令に関する推理

  • バイナリ計測フレームワークは存在する(既製品が)
    • DynamoRIO、Intel PIN、他
  • このアプローチは速やかに棄却された
    • AAAゲームの実行ファイルを計測する大規模なパフォーマンス問題

命令に関する推理

  • バイナリ計装フレームワークは存在する(既製品が)
    • DynamoRIO、Intel PIN、他
  • このアプローチは速やかに棄却された
    • AAAゲームの実行ファイルを計装する大規模なパフォーマンス問題
  • 我々の範疇では他のものが価値を持つかもしれない
    • コードを変更しないさらに誘導された動的な計装
    • “このスポットにおいてこの値はどれだけの頻繁にゼロになる?”
    • “この関数への最大/最小入力値は?“

アイデア その1

  • 何とかしてvoid TraceFunction(func_ptr)を書く
  • 何とかして、命令ごとに
    • その命令をディスアセンブルする
    • メモリのderefを見つけて、シミュレートされたキャッシュを更新する
    • 一時バッファに命令をコピーして、切り離して実行する
  • 簡単そう!

アイデア その1 --- どこでもイケるわけじゃない

  • 分岐は特別扱いする必要がある

アイデア その1 --- どこでもイケるわけじゃない

  • 分岐は特別扱いする必要がある
  • RIP(命令ポインタ)の暗黙的な仕様はそこかしこにある

アイデア その1 --- どこでもイケるわけじゃない

  • 分岐は特別扱いする必要がある
  • RIP(命令ポインタ)の暗黙的な仕様はそこかしこにある
  • Win64の例外ハンドリングは我々が侵害しているルールがある
    • OutputDebugStringは例外を使っている…

アイデア その1 --- どこでもイケるわけじゃない

  • 分岐は特別扱いする必要がある
  • RIP(命令ポインタ)の暗黙的な仕様はそこかしこにある
  • Win64の例外ハンドリングは我々が侵害しているルールがある
    • OutputDebugStringは例外を使っている…
  • 超侵入的 --- 我々のトレース関数へのトップレベル呼び出しを必要とする
    • すべてのスレッドで

突然ですが: EFLAGS

突然ですが: EFLAGS

X Trap Flag (TF) ------

アイデア その2: EFLAGSの活用

  • シングルステップ実行はCPUの機能である
    • デバッガのF11がやってるやつ
    • EFLAGSのTRAPビットをセットする

アイデア その2: EFLAGSの活用

  • シングルステップ実行はCPUの機能である
    • デバッガのF11がやってるやつ
    • EFLAGSのTRAPビットをセットする
  • WindowsのSEH機構を通る例外として転送する
    • そのためのハンドラをインストールできる

アイデア その2: EFLAGSの活用

  • シングルステップ実行はCPUの機能である
    • デバッガのF11がやってるやつ
    • EFLAGSのTRAPビットをセットする
  • WindowsのSEH機構を通る例外として転送する
    • そのためのハンドラをインストールできる
  • でも、どうやってすべてのスレッドに対するSEHハンドラをインストールするの?
    • ベクトル化した例外ハンドラ

改訂版攻略計画

  • 追跡を始めるために
    • TRAP例外をフィルタリングするためにVEHをインストールする
    • 捕捉したいすべてのスレッドに対してEFLAGSのTFビットをセットする

改訂版攻略計画

  • 追跡を始めるために
    • TRAP例外をフィルタリングするためにVEHをインストールする
    • 捕捉したいすべてのスレッドに対してEFLAGSのTFビットをセットする
  • ハンドラでは
    • 命令をディスアセンブルして、メモリオペランドを見つける
    • キャッシュシミュレーションを更新する
    • 追跡を継続させる前にTFビットを再セットする

改訂版攻略計画

  • 追跡を始めるために
    • TRAP例外をフィルタリングするためにVEHをインストールする
    • 捕捉したいすべてのスレッドに対してEFLAGSのTFビットをセットする
  • ハンドラでは
    • 命令をディスアセンブルして、メモリオペランドを見つける
    • キャッシュシミュレーションを更新する
    • 追跡を継続させる前にTFビットを再セットする
  • 追跡をやめるには
    • いくつかのフラグをセットして、(最終的に)VEHを取り除く

すべての計画は問題を抱えている

  • 良い知らせ: 基本的には上手く動作する

すべての計画は問題を抱えている

  • 良い知らせ: 基本的には上手く動作する
  • 問題その1: デバッガは本当に
    • “何?ブレークしたいの?”
    • 解決法: デタッチ状態で動作する
  • 問題その2: ntdll.dllにおける膨大な量のデッドロック
    • VEHディスパッチリストを保護している競合したSRWロックでハングする
    • ロックが起きるのを待つスレッド
    • でも、誰もロックを所有していないので、一生起きない

デッドロックの災い

  • SEHの方法を用いること、すなわち、他でもないMSで予期していた方法である
    • すべてのスレッド、すべての命令は例外ハンドリングを行うexerciseであろう
  • そのコードにおいて明らな間違いは存在しなかった
  • 疑わしい問題はクリティカルセクションにリエントラント的に干渉していることである

解決法: ntdllでのコードのロックを無経過する

  • VEHは風変わりな機能であり、一般にハンドラはインストールされない
    • これはデバッグ機能であり、プレイヤーに出荷している何かではない
    • 我々のハンドラへのジャンプでntdll!RtlpCallVectoredHandlersを粉砕するだけ
  • 醜いが、事は済む
  • また: 内部的にOSのロックは取らず、スピンロックしているだけ

[cue evil laugh]

上手くいった!

  • ひとつひとつ命令を検査できる!

上手くいった!

  • ひとつひとつ命令を検査できる!
  • メモリオペランドについて知っているディスアセンブラが必要
    • udis86のRadare2フォークがちょうどいい

上手くいった!

  • ひとつひとつ命令を検査できる!
  • メモリオペランドについて知っているディスアセンブラが必要
    • udis86のRadare2フォークがちょうどいい
uintptr_t rip = ExcInfo->ContextRecord->Rip;

ud_set_input_buffer(ud, (const uint8_t*)rip, 16);
ud_set_pc(ud, rip);
int ilen = ud_disassemble(ud);
GenerateMemoryAccesses(core_index, ud, rip, ilen, ExcInfo->ContextRecord);

メモリアクセスの生成

  • 簡単でしょ?メモリオペランドを調べるだけ
    • mov dword ptr 《rax》, ebx
  • まあ、理論的には…
    • x64では多くのものがメモリオペランドを持っている
    • いくつかはメモリにアクセスするけど、メモリオペランドをもっていない!

考慮すべき多くの特例

  • 文字列命令、例えば、LODSB、MOVSD(暗黙的なRSI/RDIのアクセス)
  • スタックのプッシュ/ポップ
  • CALL、RET(それぞれスタックの読み書きも行う)
  • LEA --- 超基本的で、メモリオペランドを持つが、メモリに触らない
  • クレイジーな”long nop”命令はメモリオペランドを持ち得る
  • FXSTOR/FXRSTOR
  • プリフェッチやnon-temporalロード/ストア

キャッシュシミュレーションを突く

// Generate I-cache traffic.
CacheSim::AccessResult r = g_Cache.Access(core_index, rip, ilen, CacheSim::kCodeRead);
stats->m_Stats[r] += 1;

キャッシュシミュレーションを突く

// Generate I-cache traffic.
CacheSim::AccessResult r = g_Cache.Access(core_index, rip, ilen, CacheSim::kCodeRead);
stats->m_Stats[r] += 1;

// Generate D-cache traffic.
for (int i = 0; i < read_count; ++i) {
  CacheSim::AccessResult r = g_Cache.Access(core_index, reads[i].ea, reads[i].sz, CacheSim::kRead);
  stats->m_Stats[r] += 1;
}

キャッシュシミュレーションを突く

// Generate I-cache traffic.
CacheSim::AccessResult r = g_Cache.Access(core_index, rip, ilen, CacheSim::kCodeRead);
stats->m_Stats[r] += 1;

// Generate D-cache traffic.
for (int i = 0; i < read_count; ++i) {
  CacheSim::AccessResult r = g_Cache.Access(core_index, reads[i].ea, reads[i].sz, CacheSim::kRead);
  stats->m_Stats[r] += 1;
}

for (int i = 0; i < write_count; ++i) {
  CacheSim::AccessResult r = g_Cache.Access(core_index, writes[i].ea, writes[i].sz, CacheSim::kWrite);
  stats->m_Stats[r] += 1;
}

二次元配列としてセットアソシアティブキャッシュをモデル化できる

入力アドレスを分解する:

二次元配列としてセットアソシアティブキャッシュをモデル化できる

入力アドレスを分解する:

そのセットの位置を突き止める

二次元配列としてセットアソシアティブキャッシュをモデル化できる

入力アドレスを分解する:

そのセットの位置を突き止める

キャッシュされているかを確認するために各ウェイとアドレスを比較する

Jaguarのキャッシュのシミュレート

  • コンソールのJaguarはモジュールを2つ持っている
    • 各モジュールは4コアで共有されるL2キャッシュを持つ
    • 各コアは自身のD1とI1キャッシュを持つ

Jaguarのキャッシュのシミュレート

  • コンソールのJaguarはモジュールを2つ持っている
    • 各モジュールは4コアで共有されるL2キャッシュを持つ
    • 各コアは自身のD1とI1キャッシュを持つ
  • Jaguarのキャッシュは侵入的である(D1/I1にあるラインはL2にも存在しなければならない)

Jaguarのキャッシュのシミュレート

  • コンソールのJaguarはモジュールを2つ持っている
    • 各モジュールは4コアで共有されるL2キャッシュを持つ
    • 各コアは自身のD1とI1キャッシュを持つ
  • Jaguarのキャッシュは侵入的である(D1/I1にあるラインはL2にも存在しなければならない)
  • セットアソシアティブ性を我々の配列構造に対応させる
    • I1: 512ライン(32KB)、2ウェイ、256セット
    • D1: 512ライン(32KB)、8ウェイ、64セット
    • L2: 32768ライン(2MB)、16ウェイ、2048セット

キャッシュの定義

// Simulating a Jaguar cache

//                      size in byte   |  assoc
using JaguarD1 = Cache<      32 * 1024,      8>;
using JaguarI1 = Cache<      32 * 1024,      2>;
using JaguarL2 = Cache<2 * 1024 * 1024,     16>;

キャッシュの定義

// Simulating a Jaguar cache

//                      size in byte   |  assoc
using JaguarD1 = Cache<      32 * 1024,      8>;
using JaguarI1 = Cache<      32 * 1024,      2>;
using JaguarL2 = Cache<2 * 1024 * 1024,     16>;

struct JaguarModule {
    JaguarD1      m_CoreD1[4];   // モジュールあたり4コア、個別のD1とI1を持つ
    JaguarI1      m_CoreI1[4];
    JaguarL2      m_Level2;      // 共有L2キャッシュ
    JaguarModule* m_OtherModule; // 無効化のための他のモジュールへのポインタ
}

擬似コードで見るキャッシュの更新

キャッシュラインがアクセスされるごとに:

擬似コードで見るキャッシュの更新

キャッシュラインがアクセスされるごとに:

書き込んでいる場合:

他のすべてのコアの外にキャッシュラインを追い出す

他のモジュールのL2の外にキャッシュラインを追い出す

擬似コードで見るキャッシュの更新

キャッシュラインがアクセスされるごとに:

書き込んでいる場合:

他のすべてのコアの外にキャッシュラインを追い出す

他のモジュールのL2の外にキャッシュラインを追い出す

ヒット1 = D1/I1でキャッシュラインを探して記録する

ヒット2 = L2でキャッシュラインを探して記録する

擬似コードで見るキャッシュの更新

キャッシュラインがアクセスされるごとに:

書き込んでいる場合:

他のすべてのコアの外にキャッシュラインを追い出す

他のモジュールのL2の外にキャッシュラインを追い出す

ヒット1 = D1/I1でキャッシュラインを探して記録する

ヒット2 = L2でキャッシュラインを探して記録する

If Hit1 && Hit2:

return kL1Hit

Else If Hit2:

return kL2Hit

Else:

return kL2Miss

動作

  • メインループでキーボードショートカットに追跡機構をフックする
    • フレーム終わりに自動的に無効化する

動作

  • メインループでキーボードショートカットに追跡機構をフックする
    • フレーム終わりに自動的に無効化する
  • データ収集は約2〜3分かかる
    • ワークロードに依存する

動作

  • メインループでキーボードショートカットに追跡機構をフックする
    • フレーム終わりに自動的に無効化する
  • データ収集は約2〜3分かかる
    • ワークロードに依存する
  • バイナリファイルに結果を蓄える
    • 我々のユースケースでは約100〜150MB

動作

  • メインループでキーボードショートカットに追跡機構をフックする
    • フレーム終わりに自動的に無効化する
  • データ収集は約2〜3分かかる
    • ワークロードに依存する
  • バイナリファイルに結果を蓄える
    • 我々のユースケースでは約100〜150MB
  • ゲームは収集後にフルフレームレートで動作を再開する!
    • オフラインでダンプを解析する

解析

  • 収集した状態は命令に関連付けられる
    • コールスタックもキャプチャされ、曖昧さをなくすのに使われる
  • 我々のセットアップでキャプチャされる現在の状態:
    • L1ヒット(I1/D1の追跡とは別)
    • L2ヒット
    • L2ミス(命令とデータをそれぞれ追跡する --- ハードウェアができることより優れている!)
    • D1またはL2にヒットする明示的なプリフェッチの数
    • 実行された命令数

ツール群 --- 状態のフラットプロファイル

ツール群 --- トップダウンツリー

ツール群 --- 逆ツリー

ツール群 --- ソース注釈

ツール群の考慮事項

  • “明らかに悪い”データをポップさせたい
    • 悪さファクタ = L2ミス^2 / 命令数
  • いろんな見方で投資する価値がある
    • ダンプ上で更なる解析を可能にすることで価値を最大化する

PushBuffer::SetTextureAssetsに話を戻すと

PushBuffer::SetTextureAssetsに話を戻すと

CacheSimの利点

  • プログラム中のメモリアクセスごとのデータを収集する
  • 非侵入的
  • 邪魔にならない
  • Windowsで動作する
    • グラフィクスドライバにさえも深く計装して、OSはシステムコールレベルに呼び出しに行く
  • オープンソース
    • 更なるシナリオに容易に拡張できる

CacheSimの欠点

  • キャプチャ速度はより良くできるかもしれない
  • Windowsでしか動作しない
    • 依然としてコンソールのワークフローのためのJaguarのキャッシュをシミュレートできる(OSのものを無視する)
  • 100%ハードウェアに正確ではない(というか、できない)
    • インオーダーCPUとしてCPUを扱う --- アウトオブオーダーのスケジューリングではない
    • キャッシュを指すのに仮想アドレスを用いなければならない(マイナーな問題)
    • 配列プリフェッチャーがシミュレートされていない(配列について過度に悲観的)
    • MESI/Store forwarding buffers/…

今後の課題

  • ハードウェアプリフェッチシミュレーション
  • non-temporal storeシミュレーション
  • キャプチャの高速化
  • 拡張

謝辞+質疑応答

Footnotes

  1. 訳注:bursty = バースト(一時的に作業量が増加すること)が高頻度で発生すること