1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【脱GC】ガベージコレクションによるカクつきを撲滅する。極限パフォーマンスのための「ゼロ・アロケーション」設計

1
Posted at

1. はじめに:なぜ「ゼロ・アロケーション」なのか?

60FPS(約16.6ms/フレーム)、あるいは120FPSといった高フレームレートが求められるゲームやシミュレーションでは、**ガベージコレクション(GC)による一時停止(スパイク)**が深刻な問題になります。

ループ内で何気なく new Vector3() を生成したり、オブジェクトを頻繁に作成・破棄したりすると、ヒープ領域が徐々に圧迫され、やがてGCが発動します。
この数ミリ秒〜数十ミリ秒の停止が、ユーザーにとって明確な「カクつき(スタッタリング)」として知覚されます。

この問題を根本から回避する設計思想が、

実行中に一切のメモリアロケーションを行わない「ゼロ・アロケーション」

です。


2. データ構造の転換:AoOからSoAへ

JavaScriptやC#のような言語では、オブジェクト(参照型)の生成は基本的にヒープへのアロケーションを伴います。

そのため、以下のような直感的なデータ構造:

  • AoO(Array of Objects)

    [{x:1, y:2}, {x:3, y:4}, ...]
    

は、GC負荷の観点では不利です。

代わりに採用するのが:

  • SoA(Structure of Arrays)

    {
      x: [1, 3, ...],
      y: [2, 4, ...]
    }
    

この構造により:

  • オブジェクト生成が不要になる
  • メモリが連続配置される
  • キャッシュ効率が向上する

といった利点が得られます。


3. Typed Arrayによるフラットなメモリ管理

SoAをさらに推し進めると、Float32ArrayUint32Array といったTyped Arrayによる管理に行き着きます。

重要なポイントは次の通りです:

  • 初期化時に最大サイズを一括確保
  • 実行中はインデックスアクセスのみ
  • オブジェクト生成・破棄は一切行わない

実装例:ゼロ・アロケーションなエンティティ管理

class ZeroAllocEntityManager {
  constructor(maxEntities) {
    this.maxEntities = maxEntities;
    this.activeCount = 0;

    // 初期化時にのみメモリ確保
    this.positionsX = new Float32Array(maxEntities);
    this.positionsY = new Float32Array(maxEntities);
    this.velocitiesX = new Float32Array(maxEntities);
    this.velocitiesY = new Float32Array(maxEntities);
    this.isActive = new Uint8Array(maxEntities);
  }

  spawn(x, y, vx, vy) {
    if (this.activeCount >= this.maxEntities) return -1;

    const i = this.activeCount;

    this.positionsX[i] = x;
    this.positionsY[i] = y;
    this.velocitiesX[i] = vx;
    this.velocitiesY[i] = vy;
    this.isActive[i] = 1;

    this.activeCount++;
    return i;
  }

  update() {
    for (let i = 0; i < this.activeCount; i++) {
      if (this.isActive[i]) {
        this.positionsX[i] += this.velocitiesX[i];
        this.positionsY[i] += this.velocitiesY[i];
      }
    }
  }
}

この実装では:

  • new はコンストラクタ時のみ
  • フレーム中のアロケーションはゼロ
  • GCは実質発生しない

という状態を実現できます。


4. GPU転送との高い親和性

ゼロ・アロケーション設計の大きな利点の一つが、GPUとのデータ連携の効率化です。

AoO(オブジェクト配列)の場合:

  • GPU用にデータを再パック(シリアライズ)する必要がある
  • その過程で追加のアロケーションが発生する

一方、Typed Arrayで管理している場合:

  • メモリがすでにフラットなバイナリ形式
  • そのままGPUバッファへ転送可能(例:writeBuffer

これにより:

  • CPU負荷の削減
  • GC発生の抑制
  • 転送コストの最小化

を同時に実現できます。

さらに、連続メモリ配置によってCPUキャッシュ効率(データ局所性)も向上し、純粋な演算性能も改善されます。


5. 運用上の注意点

ゼロ・アロケーションは強力ですが、トレードオフもあります。

  • コードの抽象度が下がる(低レベル寄りになる)
  • 可読性・保守性が低下しやすい
  • 最大数(プールサイズ)を事前に決める必要がある

そのため:

  • パフォーマンスクリティカルな箇所に限定して適用する
  • 上位レイヤーでは通常のオブジェクト設計を維持する

といったバランスが重要です。


6. まとめ

ゼロ・アロケーションは、

「オブジェクトではなく、データとして扱う」

という発想の転換に基づく最適化手法です。

  • GCスパイクの排除
  • CPUキャッシュ効率の向上
  • GPU転送の高速化

といった複数のメリットを同時に得られる一方で、設計にはある程度の割り切りが求められます。

数万〜数十万規模のデータをリアルタイムで扱う場合、このアプローチは非常に有効です。
パフォーマンスの限界を押し上げたい場面で、ぜひ選択肢に入れてみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?