クラッシュ
最適化

プロファイリング

Unityプロファイラが使用するポートは: MulticastPort : 54998 ListenPorts : 55000 - 55511 Multicast(unittests) : 55512 - 56023 ネットワーク ノードの中からアクセス出来る必要があります。つまりプロファイリングを行うデバイスはプロファイラを有効にしてUnityエディター上でマシンのポートをみることが出来る必要があります。

最初のステップ

Unityはスキニング,バッチング,物理計算,ユーザスクリプト,パーティクルについてCPUに依存します(部分的にはSIMD,例えばx86ならSSE,ARMならNEON)。

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 Start ()_ {
   while (true) {
      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メモリおよび Mono メモリがあります

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 - profile your own code
  • 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 な monobehavior フィールドのような,異様なキャッシュに入れてないか
クラッシュ
最適化