Technology

新しい e ブックのご紹介:Unity の上級開発者のための Data-Oriented Technology Stack(DOTS)

THOMAS KROGH-JACOBSEN / UNITY TECHNOLOGIESProduct Marketing Core Tech
May 30, 2024|9 分
新しい e ブックのご紹介:Unity の上級開発者のための Data-Oriented Technology Stack(DOTS)

Unity の Data-Oriented Technology Stack(DOTS)は、ターゲットハードウェアを最大限に活用するためのパフォーマンス向上ツール一式を提供することで、複雑なゲームを大規模に作成できます。

この度、50 ページを超える e ブック「上級 Unity 開発者のための Data-Oriented Technology Stack 入門」が無料でダウンロードできるようになりました。データ指向プログラミングの理解を深め、次に取り組むプロジェクトに DOTS が適しているかどうか判断するための手引きとしてご活用ください。本ガイドは DOTS ベースの新規プロジェクトを立ち上げようとしている方にとっても、Monobehaviour ベースのゲームのパフォーマンス上重要な部分に DOTS を実装しようとしている方にとっても、必要なすべての項目を体系的かつわかりやすい形でカバーしています。

Unity 6 がプレビュー版として公開され、DOTS 1.0 が本番環境で利用可能になった今こそ、DOTS がもたらす可能性を探る絶好の機会です。Unity のシニアソフトウェアエンジニアである Brian Will 氏によって執筆されたこの e ブックは、更新された Unity Learn のサンプル、最近の DOTS ブートキャンプ、GitHub のサンプルに加わる形で、DOTS の使い方を学びたい開発者が利用できるリソースコレクションの仲間入りを果たしました。

DOTS がゲームに適しているかどうかを判断するためのガイド
Unity の Entity Component System において、同じタイプのコンポーネントセットを持つすべてのエンティティは、同じ「アーキタイプ」にまとめて格納されます。

この e ブックの目的は、既存または今後の Unity プロジェクトに DOTS パッケージやテクノロジーを部分的または全面的に導入することが適切かどうか、十分な情報を得た上で判断するためのサポートを提供することです。スタックの各要素は、ゲームの実行速度と効率を高める役割を果たします。このガイドは、これらの各要素やその連携方法、および共通の基盤である Unity Entity Component System(ECS)について説明することを目的としています。

DOTS を使用する主な理由は、ターゲットハードウェアから最大限のパフォーマンスを引き出すことで、そのためにはマルチスレッドとメモリ割り当てを理解する必要があります。さらに、DOTS を活用するには、データ指向のコードとプロジェクトを、抽象度の高い C# ベースの Monobehaviour プロジェクトとは異なる方法で設計する必要があります。

e ブックの内容を詳しく見てみましょう。

CTA:上級 Unity 開発者のための Data-Oriented Technology Stack 入門」を今すぐダウンロード。

DOTS e ブックの内容
 EntityComponentSystemSamples GitHub 内の「Firefighters」サンプルの一場面。

以下に掲載するガイドの最初のセクションでは、ガベージコレクションのオーバーヘッド、キャッシュフレンドリーではないデータやコード、コンパイラーが生成する最適ではないマシンコードなど、ゲームの CPU パフォーマンスを低下させる要因をいくつか紹介しています。

次のセクションでは、DOTS の各パッケージや機能が、どのように CPU パフォーマンスの落とし穴を回避するコードの作成を促進するかを説明しています。以下についての有益な説明が掲載されています。

  • C# Job System
  • Burst コンパイラー
  • Collections
  • Mathematics
  • Entities
  • Entities Graphics
  • Unity Physics
  • Netcode for Entities

スタックの各要素の概要の後には、GitHub リポジトリ EntityComponentSystemSamples の紹介が続きます。このリポジトリには、基本的な DOTS 機能と高度な DOTS 機能の両方を紹介するサンプルが多数含まれています。Github リポジトリにあるサンプルの一部は、Unity Learn の新しい DOTS コース、「Get acquainted with DOTS」で再現されています。

DOTS ガイドのもう一つの重要なセクションは付録です。ここでは、Brian Will 氏が、Unity ECS に関連する概念(メモリ割り当てとガベージコレクション、メモリと CPU キャッシュ、マルチスレッドプログラミング、オブジェクト指向プログラミングの制限、データ指向プログラミングなど)について詳しく解説しています。

抜粋:パフォーマンスについて
Burst コンパイルされたジョブが CPU の能力を活用し、多くのワーカースレッドで実行されていることを示す、Unity Profiler のプロファイル。

経験豊富なゲーム開発者であれば、ターゲットプラットフォームにおけるパフォーマンスの最適化が開発サイクル全体に関わる作業であることを知っているでしょう。ハイエンドの PC ではゲームがうまく動作するかもしれませんが、ローエンドのモバイルプラットフォームではどうでしょうか?一部のフレームのロード時間が他のフレームより長く、顕著ながたつきが生じることはありませんか?ロード時間が長くてイライラしたり、プレイヤーがドアをくぐるたびにゲームが数秒間フリーズしたりはしませんか?このような状況では、現在の体験が劣っているだけでなく、機能を追加することも実質不可能になります。環境の細かなディテールやスケール、メカニクス、キャラクターや行動、物理演算、プラットフォームなど、すべて追加できなくなってしまうのです。

このような現象の原因は何でしょうか?多くのプロジェクトにおいてはレンダリングが原因です。テクスチャが大きすぎたり、メッシュが複雑すぎたり、シェーダーが高コストすぎたり、バッチ処理、カリング、LOD が効果的に使われていなかったりします。

もう一つのよくある落とし穴は、物理シミュレーションのコストを増大させる、複雑なメッシュコライダーの過剰な使用です。あるいは、ゲームシミュレーションそのものが遅いのかもしれません。ゲーム独自の要素を実装している C# コードの、1 フレームあたりの CPU 時間が長すぎる可能性もあります。

では、どうすれば速いコードや、少なくとも遅くはないゲームコードを書けるのでしょうか?

過去数十年間において、PC ゲーム開発者は、ただ待つことでこの問題を解決できることが多くありました。1970 年代から 21 世紀にかけて、CPU のシングルスレッド性能は一般に数年ごとに 2 倍になった(ムーアの法則と呼ばれる現象)ため、PC ゲームはそのライフサイクルを通じて「魔法のように」高速化しました。しかし、過去 20 年間における CPU のシングルスレッド性能の向上は比較的緩やかでした。その代わり、CPU のコア数は増加の一途をたどっており、現在ではスマートフォンのような小型ハンドヘルドデバイスにさえ複数のコアが搭載されています。さらに、ハイエンドとローエンドのゲームデバイス間の格差が広がり、プレイヤー層の多くが数年前のハードウェアを使用しています。より高速なハードウェアの登場を待つことは、もはや実行可能な戦略とは言えません。

よって、ここで問われるべきは「そもそもこの CPU コードはどうして遅いのか?」

  • ということです。よくある落とし穴はいくつかあります。ガベージコレクションが顕著なオーバーヘッドや一時停止の原因になる:これは、ガベージコレクターがアプリケーションのメモリの割り当てと解放を管理する自動メモリマネージャーとして機能するためです。ガベージコレクションは CPU とメモリのオーバーヘッドを伴うだけでなく、コードの実行を何ミリ秒も一時停止させることがあります。
  • このような一時停止は、小さながたつきとして、あるいはユーザー体験をより損なうスタッターとして現れるかもしれません。コンパイラーが生成するマシンコードが最適ではない:コンパイラーによっては、生成するコードの最適化が他のコンパイラーより遥かに劣っているものもあり、その結果はプラットフォームによって異なります。
  • CPU コアが十分に活用されていない:いまや最もローエンドなデバイスにもマルチコア CPU が搭載されていますが、マルチスレッドコードを書くのは難しくエラーも発生しやすいことが多いため、多くのゲームはロジックのほとんどをメインスレッドで実行しています。データがキャッシュフレンドリーではない:キャッシュデータへのアクセスは、メインメモリからデータをフェッチするよりもはるかに高速です。
  • しかし、システムメモリにアクセスすると、CPU が何百 CPU サイクルも待機しなければならない場合があります。そのため、可能な限り CPU がキャッシュからデータを読み書きするようにするのが良いでしょう。これを可能にする最も単純な方法は、メモリの読み書きを順番に実行することです。 隙間のない連続した配列としてデータを格納することで、最もキャッシュフレンドリーな読み書きを実現できます。逆に、データがメモリ全体に非連続的に散らばっている場合、データへのアクセス時に負荷の高いキャッシュミスが大量に発生しやすくなります。
  • CPU が要求するデータがキャッシュメモリに存在せず、代わりに低速のメインメモリからデータをフェッチする必要が生じます。コードがキャッシュフレンドリーではない:コードがキャッシュに存在しない場合、そのコードを実行する際に、システムメモリからロードする必要があります。推奨される戦略の一つとして、システムメモリからのロード回数を減らすために、できるだけ少ない場所で関数を呼び出すようにすることが挙げられます。例えば、特定の関数をフレーム内のさまざまな場所で呼び出すよりも、1 つのループ内で呼び出す方が、1 フレームにつき最大 1 回のロードで済むため効率的です。
  • コードが過度に抽象化されている:その他の問題に加え、抽象化はデータとコードの両方に複雑さをもたらすため、前述の問題が悪化しやすくなります。ガベージコレクションなしでは割り当てを管理するのが難しくなり、コンパイラーの最適化効率が下がる可能性があり、安全で効率的なマルチスレッドが難しくなり、データとコードがキャッシュフレンドリーでなくなる傾向があります。その上、抽象化によってパフォーマンスコストが分散され、コード全体が遅くなり、最適化すべき明確なボトルネックがなくなってしまう傾向があります。

上記の問題はすべて、Unity プロジェクトでよく見られるものです。もう少し具体的に見てみましょう。

  • C# では手動で割り当てられたオブジェクト(ガベージコレクションされないオブジェクト)を作成できますが、C# やほとんどの Unity プロジェクトでは、ガベージコレクションされる C# のクラスインスタンスを使用するのがデフォルトです。実際には、Unityユーザーは長い間、プーリングと呼ばれる手法でこの問題を軽減してきました(プーリングが、そもそもガベージコレクション言語を使用する目的を打ち消してしまうにもかかわらずです)。オブジェクトプーリングの主な利点は、事前に割り当てられたプールからオブジェクトを効率的に再利用することで、オブジェクトの頻繁な生成と割り当て解除を不要にすることです。
  • Unity エディターでは、C# コードは通常、Mono コンパイラーでマシンコードにコンパイルされます。スタンドアロンビルドの場合、IL2CPP(C# 中間言語を C++ にクロスコンパイルしたもの)を使えばより良い結果が得られますが、ビルドの時間が長くなったり、MODのサポートが難しくなるなどのデメリットが存在します。
  • Unity では、簡単にすべてのコードをメインスレッドで実行できるため、Unity プロジェクトではこの手法を取るのが一般的です。
  • MonoBehaviour の Update() メソッドなど、Unity のイベント関数はすべてメインスレッドで実行されます。
  • ほとんどの Unity API は、メインスレッドからしか安全に呼び出すことができません。
  • 一般的な Unity プロジェクトのデータは、ランダムなオブジェクトがメモリ全体に散らばる構造に陥る傾向があり、キャッシュの利用効率が悪くなります。これもまた、Unity では簡単にそうできてしまうからというのもあります。
  • ゲームオブジェクトとそのコンポーネントはすべて個別に割り当てられるため、メモリ上の別々の場所に保存されることが多くなります。
  • 一般的な Unity プロジェクトのコードは、キャッシュフレンドリーではない傾向があります。
  • 従来の C# と Unity の API は、オブジェクト指向のコードスタイルを推奨しており、多数の細分化されたメソッドや複雑な呼び出しチェーンが生じやすくなります。データ指向のアプローチとは異なり、ハードウェアにはあまり優しくありません。
  • 各 MonoBehaviour のイベント関数は個別に呼び出されますが、その呼び出しは必ずしも MonoBehaviour のタイプごとにグループ化されているわけではありません。例えば、モンスターの MonoBehaviour が 1000 個あった場合、各モンスターは別々に更新され、必ずしも他のモンスターと一緒に更新されるわけではありません。

従来の C# や多くの Unity API に見られるオブジェクト指向のスタイルは、一般的に抽象度が高いソリューションにつながります。この結果として生まれたコードには非効率的な部分が散見され、それを切り離すことは難しいでしょう。

DOTS e ブックの対象ユーザー
「上級 Unity 開発者のためのData-Oriented Technology Stack 入門」e ブックからのプレビュー。

この e ブックは誰でも無料で閲覧できますが、Monobehaviour をベースとしたオブジェクト指向でのゲーム開発経験はあるものの、Unity DOTS やデータ指向でのデザイン開発経験はない Unity 開発者向けに作られています。

このガイドが、Unity プロジェクトにおける DOTS やその機能の活用方法について理解し、Unity の GitHub リポジトリで公開中のサンプルを最大限活用する上で役立つことを願っています。