キャッシュ局所性と AoS / SoA¶
一言で言うと¶
- キャッシュ局所性 (cache locality): 「次に使うデータが、既にキャッシュに載っている」度合い。空間的局所性(近くのデータを使う)と時間的局所性(同じデータを繰り返し使う)がある(ハードの仕組みは → キャッシュ)
- AoS (Array of Structures): 構造体の配列
Particle particles[N]— オブジェクト指向の自然な形 - SoA (Structure of Arrays): 配列の構造体
float xs[N]; float ys[N];— 処理単位でデータを揃える形
問題: AoSの「無駄な同乗者」¶
// C++20 — AoS: 1粒 = 48バイト
struct Particle {
float x, y, z; // 位置(移動処理で使う: 12バイト)
float vx, vy, vz; // 速度(移動処理で使う: 12バイト)
float r, g, b, a; // 色(移動処理では使わない: 16バイト)
float lifetime; //(使わない: 4バイト)
int textureId; //(使わない: 4バイト)
};
std::vector<Particle> particles;
void Move(float dt) {
for (auto& p : particles) { p.x += p.vx * dt; p.y += p.vy * dt; p.z += p.vz * dt; }
}
// キャッシュライン(64バイト)には位置・速度と一緒に「色・寿命・テクスチャID」も載ってくる。
// 帯域の半分は使わないデータの運搬 = 実効速度が半分になる
SoA: 使うものだけを隙間なく¶
// C++20 — SoA
struct Particles {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
std::vector<float> r, g, b, a;
std::vector<float> lifetime;
std::vector<int> textureId;
std::size_t count = 0;
};
void Move(Particles& ps, float dt) {
for (std::size_t i = 0; i < ps.count; ++i) {
ps.x[i] += ps.vx[i] * dt; // xの配列は position データ100%の密度でラインに載る
ps.y[i] += ps.vy[i] * dt;
ps.z[i] += ps.vz[i] * dt;
}
}
検証済みサンプル: samples/aos_soa_bench.cpp(同一処理のAoS/SoA時間比較)
- 帯域の無駄がゼロ+SIMD自動ベクトル化の理想形(連続同型データ)
- ECSのComponent配列は、この考えをアーキテクチャ化したもの
トレードオフ(SoAは無料ではない)¶
| AoS | SoA | |
|---|---|---|
| 1個の全フィールドを使う処理(個体の生成・削除・シリアライズ) | 速い(1ラインに全部) | 遅い(N配列に飛ぶ) |
| 一部フィールドを全個体に(移動・物理) | 帯域を無駄にする | 最速 |
| コードの書き味 | 自然(p.x) |
崩れる(ps.x[i])。追加・削除がN配列の同期作業 |
| 「1個のパーティクル」を関数に渡す | Particle& で済む |
インデックス+コンテナ参照が要る |
中間形 (AoSoA): ホットなフィールド群だけ構造体にまとめて配列化(PosVel{x,y,z,vx,vy,vz} の配列+コールドデータは別配列)。実務ではこの「ホット/コールド分離」で十分なことが多いです。
判断基準¶
- 計測が先: プロファイラでメモリ律速(キャッシュミス率)と出た処理だけが対象
- 個体数の目安: 数百ならどうでもよい。数万〜で差が出始め、数十万で支配的
- アクセスパターンで決める: 「全個体×一部フィールド」の処理が主ならSoA、「個体単位の雑多な処理」が主ならAoS
C#/Unityとの対応¶
- C#の
List<Particle>(classなら)はAoS以前の問題: 参照の配列で実体はヒープに散在(ポインタ追跡)。structのList/配列でようやくAoS相当 - Unity DOTSの
NativeArray<T>+Burst、ECSのアーキタイプ(チャンク)は、まさにSoA/AoSoAの実装。「なぜDOTSはああいう書き方を強いるのか」の答えがこのページ - VFX GraphやパーティクルシステムのGPU実装もSoA(GPUはさらに局所性・整列に敏感)
Unity開発者が誤解しやすい点¶
- 「オブジェクト指向的に自然な形が普通」— 大量データではデータの形は処理が決める(データ指向設計)。個体という単位はUI/ロジック上の概念で、CPUには配列しか見えていない
- 「structならなんでも速い」— structでもフィールドが太ればAoSの無駄同乗問題は起きる
- SoA化を全部に適用しない——書き味の悪化は保守コスト。ホットな数か所だけ
ゲーム開発での使用例¶
- パーティクル、弾幕、群衆、ボイド、スキニング行列、物理ブロードフェーズのAABB配列
- サーバーサイドの大量エンティティシミュレーション
- ロード時間: アセットを「使う順に並べる」のもディスクの局所性という同じ話
理解度チェック¶
- AoSの「無駄な同乗者」問題をキャッシュラインの語彙で説明できますか。
- SoAが苦手な操作は何ですか。
- AoSoA(ホット/コールド分離)はどんな妥協ですか。
演習¶
samples/aos_soa_bench.cpp で要素数を1万/10万/100万と変えて実行し、差が出始める規模を確認してください。また「削除(swap-and-pop)」をSoAに実装し、AoSと比べて何か所の同期が必要になるか数えてください。
前: 並行処理のバグ | カテゴリ目次 | 次: placement new・allocator・pool・arena