🐈

データ指向設計

に公開

UnityのDOTSやECSでも採用されているデータ指向設計(Data Oriented Design)についてまとめる。

Motivation

今日においては、CPUの処理速度に対して、メインメモリのIOは非常に鈍足。

  • CPUの処理の多くがメモリからのIO待ちになっているのが現状である
  • メインメモリ→L2 キャッシュ→L1 キャッシュ→CPU Coreというデータフローが起きるが、キャッシュに対してメモリが遅い
  • キャッシュラインを64bitと仮定して、一度のデータ読み込みの時に必要なデータ量が10bit程度だとすると、54bit分無駄にしている(キャッシュミス)
  • なので最適なメモリ配置を頑張ると、メモリバンドが最適化され、キャッシュミスが減り、メモリへの読み込み処理が減らせる

Solution

オブジェクト指向のように、データと処理の組み合わせ(メンバ変数とメンバ関数)という考え方ではなく、データと処理をそれぞれ分けて考える。

  • 木構造的なデータ構造になるのではなく、データ自体はテーブル構造にする
  • つまり同じ型のデータを連続したメモリ配置にすることによって、Motivation を達成する

具体的な実装としては、データを保持するための構造体(Component)を高い粒度で用意し、それらデータに対してどのような処理をするのかを関数(Component System)として別に記述する。
各関数はその処理を行うために必要なデータセット(Entity)を宣言しておき、内部システムは保持しているデータからEntityに適するデータセットを作り出すことが可能な場合は、その関数を呼び出す。

これはParticle Systemと非常に類似している。
Particle SystemのEmit処理をしている部分がComponent System、Particle Systemに搭載されている各パラメーターがComponent、Particle毎に異なるMaterialやTextureなども含めた実際にEmitするために必要なデータ群がEntityという形である。

Advantage

  • 並列化
    • CPUが処理をする瞬間においては、データと処理が1対1で紐づくので、入力データ→関数→出力データというシンプルな流れになるので、並列化が用意となる
  • キャッシュ効率とパフォーマンス
    • キャッシュ効率の良いメモリアクセス、つまり最適なメモリ配置によってもたらされるメモリバンドの最適化、それによってキャッシュミスが減りメモリのIOが減るので、パフォーマンスが向上する
  • モジュール性
    • 関数とデータが分離しているので、関数だけを別に流用するのが容易となる
    • データフォーマットさえ合っていれば、どのようなデータであるかは問題ではないからである(逆に問題となるのであれば、関数をより小さくするべきかもしれない)
  • テスト性
    • オブジェクト指向のテストは正直ダルい
    • データ指向設計においては、必要な一連の入力データを用意した後は関数を呼び出すだけなのでシンプルとなる
    • さらに入力⇔出力の紐づけが明確となる

Disadvantage

  • 直感的ではない
    • オブジェクト指向に慣れ親しんでいる人ほど感覚とのズレを感じるかもしれない
    • データと処理を分割して考えられる思考力が必要となる
  • プログラムが肥大化しやすい
    • データと処理を細かい単位で分けてしまうので、必然的に肥大化しやすい
    • この場合の肥大化とは、プログラムの規模ではなく、構造体の数や関数の数のことを指している
    • DRYの原則に反しやすくなり、プロジェクト全体を見渡せる広い視野が求められるようになる
GitHubで編集を提案

Discussion