クラッシュ
最適化

プロファイリング

最初のステップ

Unity でのスキニング、バッチング、物理計算、ユーザースクリプト、パーティクルなどは CPU に依存します( x86 の SSE や、ARM の NEON など、 SIMD 部分に最適化しています )。

GPU はシェーダー、ドローコール、イメージエフェクトで使用されます。

CPU の制約か GPU の制約か

  • CPU および GPU の処理時間( ms )を検知するのに内部プロファイラーを使用してください。

パレート分析

大部分の問題 (80%) は少数の問題(20%)により引き起こされます。エディタープロファイラーを使用してもっとも問題となる関数コールをみつけて最初に最適化します。多くの場合、それらの重大な部分を最適化するだけで、全体的な実行速度を改善させることができます。

スクリプトが必要なときだけ実行されていることを確認してください。例えば、アクティブである必要がないオブジェクトを見つけて OnBecameVisible/OnBecameInvisible を使用して無効化してください。毎フレームごとに実行する必要がない場合コルーチンを使用します。

// Do some stuff every frame:
void Update () {
}

//Do some stuff every 0.2 seconds:
IEnumerator SlowUpdate () {
   while (true) {
      //do something
      yield return new WaitForSeconds (0.2f);
   }
}


.NET System.Threading.Thread クラスを使用して重い処理は別スレッドにします。これによりマルチコア実行できますが UnityAPI はスレッドに完全には対応していません。バッファ入力や結果や読み込みはメインスレッドに割り当てします。

CPU プロファイリング

ユーザーコードのプロファイリング

すべてのユーザーコードがプロファイラーに表示されるわけではありません。しかし Profiler.BeginSample および Profiler.EndSample を使用して必要なユーザーコードをプロファイラーに表示させることができます

GPU プロファイリング

Unity エディタープロファイラーは現時点では GPU データを表示できません。我々はハードウェア製造元と協力をしながらエディタープロファイラーに Tegra デバイスでは表示できるように進めています。

iOS 向けツール

  • Unity 内部プロファイラー(エディタープロファイラーではなく)。これはシーン全体の GPU 時間を表示します。
  • PowerVR PVRUniSCo シェーダーアナライザ。以下を参照してください。
  • iOS: Xcode OpenGL ES ドライバ Instruments ではハイレベルな情報のみが表示されます:
    • “Device Utilization %” - 全体の中でレンダリングに要した GPU 時間。95%以上の場合は GPU 制約を受けていることを示します。
    • “Renderer Utilization %” - ピクセル描画に要した GPU 時間。
    • “Tiler Utilization %” - 頂点処理に要した GPU 時間。
    • “Split count” - フレームスプリット数、すなわち割り当てられたバッファに当てはまらなかった頂点データ

PowerVR はタイルベースディファードレンダラーであり、ドローコール毎の GPU タイミングを取得することは不可能です。しかしシーン全体の GPU 時間を取得するのに Unity のビルトインプロファイラーを使用できます( Xcode に結果出力する分)。現在 Apple ツールは GPU およびそのパーツがどれだけビジーかのみ伝達し、ミリ秒単位で知ることはできません。

PVRUniSCo によりシェーダーコードの中のシェーダー全体のサイクルを Windows および Mac ともに知ることができます。 しかし Apple ドライバの状況を正確には反映できていません。それでもラフな指標としては十分です。

Android ツール

  • Adreno (Qualcomm)
  • NVPerfHUD (NVIDIA)
  • PVRTune、PVRUniSCo (PowerVR)

Tegra では、NVIDIA から最高級のパフォーマンス測定ツールが提供されていて実現したいことのすべてができます - ドローコール毎の GPU 時間、シェーダー毎のサイクル、強制 2x2 テクスチャ、Null ビュー四角形、で Windows、OSX、Linux にて動作します。PerfHUD ES はコンシューマデバイスで上手く動作しないため NVIDIA の development board が必要です。

Qualcomm により優秀な Adreno Profiler (Windows のみ) が提供されていて Windows のみですが、タイムライングラフ、フレームキャプチャ、API コール、シェーダーアナライザ、ライブ編集の機能があります。

グラフィックス関連 CPU プロファイリング

内部プロファイラーによりモジュール毎のよい概要が得られます:

  • OpenGL ES API に要した時間
  • バッチング効率
  • スキニング、アニメーション、パーティクル

プロファイラー ポート

Unity プロファイラーが使用するポートは:

  • MulticastPort : 54998
  • ListenPorts : 55000 - 55511
  • Multicast (unittests) : 55512 - 56023

ネットワークノードの中からアクセスできる必要があります。つまりプロファイリングを行うデバイスはプロファイラーを有効にして Unity エディター上でマシンのポートをみることができる必要があります。

メモリ

Unity メモリーと Mono メモリーの2種類があります。

Mono メモリー

Mono メモリはスクリプトオブジェクト、Unity オブジェクトのラッパー(ゲームオブジェクト、アセット、コンポーネント、その他)をハンドリングします。ガベージコレクションにより、利用可能なメモリへの割り当てができなかった場合、または System.GC.Collect() コールの場合、クリーンアップを行います。

メモリはヒープブロックに割り当てされます。ヒープブロックはアプリが閉じられるまで Mono に保持されます。すなわち Mono は使用メモリを OS に対して開放しません (Unity 3.x)。特定の量のメモリを割り当てると Mono のために予約されて OS では利用可能ではありません。リリースしたときも Mono で内部的に利用可能となるのみで OS にとつては利用可能ではありません。プロファイラーのヒープメモリは増加するのみで、減ることがありません。

もしシステムが割り当てたヒープブロックに新しいデータを当てはめる領域が十分にない場合、Mono により “GC” がコールされ、新しいヒープブロックを割り当てできるようになります(例えばフラグメンテーションにより)

ヒープのセクションがありすぎるということは Mono メモリを使い切ったことを意味します(フラグメンテーションまたは重い処理により)。

System.GC.GetTotalMemory を使用して Mono でメモリ使用量の合計を取得します。

一般的なアドバイスとしては、できるかぎり割り当ては小さくします。

Unity メモリ

Unity メモリはアセットデータ(テクスチャ、メッシュ、音声、アニメーション、等)、ゲームオブジェクト、エンジン内部処理(レンダリング、パーティクル、物理計算、その他)をハンドリングします。 Profiler.usedHeapSize を使用して Unity メモリ使用量の合計を取得します。

メモリマップ

ツールはまだ存在しませんが次のものが使用できます。

  • Unity Profiler - 完璧ではなく、スキップするものもあるが、概要情報を得ることができます。デバイス上でも動作します。
  • 内部プロファイラー . 使用済みヒープおよび割り当て済みヒープが表示されます。mono メモリを参照してください。フレーム毎の mono メモリ割り当てを表示します。
  • Xcode ツール - iOS
  • Xcode Instruments Activity Monitor - Real Memory 列をみてください
  • Xcode Instruments Allocations - 作成済みおよび有効なオブジェクトの割り当て
  • VM Tracker (テクスチャは IOKit ラベルにより割り当てられ、メッシュは VM Allocate に入ります。)

また、Unity API を呼び出して自身でツールを作成することができます:

  • FindObjectsOfTypeAll (type : Type) : Object[]
  • FindObjectsOfType (type : Type): Object[]
  • GetRuntimeMemorySize (o : Object) : int
  • GetMonoHeapSize
  • GetMonoUsedSize
  • Profiler.BeginSample/EndSample - 自分で実装したコードをプロファイリング
  • UnloadUnusedAssets () : AsyncOperation
  • System.GC.GetTotalMemory/Profiler.usedHeapSize

ロードされたオブジェクトへの参照 - これを知る方法はありません。回避方法としてパブリック変数の参照を Find することです。

ガベージコレクター

  • システムが割り当て済みヒープブロックに新しいデータ領域を確保できない場合に実行
  • モバイルで OnGUI() は使用しないこと: 毎フレーム数回実行され、ビューを完全に書き換えることになり、ガベージコレクション実行を必要とする大量のメモリ割り当てをコールしてしまいます。

多くのオブジェクトの作成/破棄が頻繁に行い過ぎていませんか?

  • これは断片化につながる可能性があります。
  • メモリの行動を追跡するために、エディターのプロファイラーを使用してください。
  • 内部プロファイラーを使用して mono メモリ活動のトラッキングを実施可能です。
  • 処理低下があっても問題ないときに System.GC.Collect() という .Net 関数を使用可能です。

ちょっとした割り当ての問題

  • 割り当て済み、再利用可能なクラスインスタンスを使用して自身のメモリ管理スキームを実装
  • フレームごとに膨大なメモリ割り当てをせず、代わりにキャッシュして事前割り当てすべき
  • 断片化の問題?

メモリプールを事前に割り当てる

  • 非アクティブなゲームオブジェクトの一覧を保持してインスタンス化、破棄の代わりに再利用

mono メモリの不足

  • メモリ活動のプロファイリング - 最初のメモリはいつ一杯になっているか?
  • ひとつのメモリページでは足りないほどゲームオブジェクトは本当に必要ですか?
  • ローカルデータではクラスの代わりに構造体を使用してください。ヒープ上にクラスは格納され、構造体はスタックに格納されます。
  • 自動メモリ管理を理解するページを参照してください。

メモリ不足によるクラッシュ

どこかの時点でゲームはメモリ不足 “out of memory” でクラッシュするかもしれませんが、理論的には十分なはずです。これが発生した場合、通常のゲームメモリ使用量と今回割り当てされたメモリの大きさを比較します。もし数字にかなり差がある場合、メモリの急増が発生しています。これの要因としては:

  • 同時に二つの大きなシーンがロードされる - 二つの大きなシーンの間に空のシーンを挟んでこれを解消
  • 追加シーンローディング - 未使用のパーツを取り除いてメモリサイズを維持
  • メモリにロードされた巨大なアセットバンドル
  • 適切な圧縮のないテクスチャ(モバイルでは絶対にしない)
  • Get/Set ピクセルを有効化したテクスチャ。非圧縮のテクスチャがメモリに必要となります
  • 実行時に JPEG/PNG からロードされたテクスチャは本質的に非圧縮
  • 大きな mp3 ファイルで、decompress on loadin (ロード時に解凍)とマーキングされたもの
  • キャッシュされた static な MonoBehaviour フィールドのような、シーンが変更してもクリアされずに未使用アセットとして残り続けている場合
クラッシュ
最適化