概要
- Java開発経験 からくる「巨大クラス」への慣れとその弊害
- キャッシュラインやページサイズ の理解がパフォーマンスに直結
- Struct of Arrays(SoA)とArray of Structs(AoS) の違いによるキャッシュ効率
- アクセスパターン(順次・ランダム) によるキャッシュヒット率の違い
- 構造体サイズとワーキングセット の管理が性能最適化の鍵
ハードウェアの基礎知識とキャッシュ構造
- Javaの現場 では、新機能追加時に既存クラスへフィールドやメソッドを追加しがち
- フィールド追加コスト は軽視されがちだが、ハードウェア的には無視できない影響
- パフォーマンス評価 はアルゴリズムのオーダー(O(N)など)に偏りがちだが、実際にはハードウェアの理解が重要
- lscpuコマンド でキャッシュサイズやインスタンス数を確認可能
- L1d: 352 KiB(10インスタンス)→1コアあたり約35 KiB
- L2: 10 MiB(5インスタンス)→1ペアあたり約2 MiB
- L3: 12 MiB(共有)
- キャッシュラインサイズ は64バイト(getconfコマンドで確認)
キャッシュラインとデータ配置の影響
- 1バイト読み込み時、周辺64バイトがまとめてキャッシュラインにロードされる
- 空間的・時間的局所性 を活かす設計が高速化のカギ
- Jeff Deanの“Latency numbers” を参考に、レイテンシとキャッシュ階層を把握
- L1d: 約35 KiB/コア、1-2ns
- L2: 約2 MiB/コアペア、4-5ns
- L3: 12 MiB共有、10-15ns
- DRAM: 60-100ns
Array of Structs(AoS) vs Struct of Arrays(SoA)
- Array of Structs(AoS) :各Monster構造体(例:64バイト)が配列状に並ぶ
- 1キャッシュラインに1体分だけ格納
- is_aliveだけを取り出す場合でも、他の無関係データもキャッシュされ非効率
- Struct of Arrays(SoA) :各フィールドごとに配列化
- is_alive配列なら64体分が1キャッシュラインに収まる
- キャッシュ効率が大幅向上、最大30倍の高速化も観測
構造体サイズ・アクセスパターンとパフォーマンス
- 構造体サイズが大きいほど、ワーキングセットサイズも増加
- 例:512体×64B=32KiB(L1dに収まる)、128Bなら64KiB(L2に溢れる)
- 順次アクセス ならCPUプリフェッチャが予測しやすく高速
- ランダムアクセス ではキャッシュヒット率が急落
- ハッシュマップやツリー、グラフ探索などで顕著
- ワーキングセットがキャッシュに収まらないと、レイテンシが一気に増加
ワーキングセットサイズ管理の重要性
- 構造体を小さく保つ ことで、高速なキャッシュ階層にデータを収めやすくなる
- ランダムアクセスパターン では、構造体サイズやデータ量がパフォーマンスを左右
- 実装時はデータレイアウトとアクセスパターンを意識 し、ハードウェア特性を最大限活用する設計が重要
このように、 構造体の設計やデータ配置、 アクセスパターン の工夫が、単なるアルゴリズムの理論的オーダー以上に 実際のパフォーマンス へ大きく影響します。 キャッシュ階層・ワーキングセットサイズ・プリフェッチャの挙動 を理解し、 データレイアウト最適化 を積極的に検討することが、現代のソフトウェア高速化の鍵です。