simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:パフォーマンス表示uGUIの作り方(2018版)

最近 ECS + Job Systems + Burst Compile と SRP の習熟度を上げましたが、今度は開発中にゲーム内のUIでパフォーマンスをチェックする機構について興味がわいたので
最新の Unity のパフォーマンス計測テクニックを参考に作ってみたいと思います。

ちょっと豆知識ですが、職場の先輩がパフォーマンスを損なうコードを書いた人を犯罪者と呼ぶのですよ。
たとえばプロファイルしたときのCPU処理時間でスパイクが立つとき、その原因となった関数を組み込んだ人を犯罪者と呼びます。

今回手掛けるツールは「あー、なんか犯罪したかも」を、素早く発見することを提供するものです。
他人が書いたコードに手を入れずに
「あなたの書いたこの関数が遅いと思うんです」→「あなたの書いたこの関数が処理時間の8割で30ms要しています、高速化の検討・対処が現在のチームの最優先課題になっています!」
と第一声の内容が変わって、チームメンバーが各々の作業に深く納得しながらお仕事できるようになるわけです、とても素敵ですね!

まずは cedec のこの講演を見てみましょう。

www.slideshare.net

まずはおさらい
Unity でパフォーマンス計りたいと思ったら
Window > Analysis の下にある
Profiler (全体の負荷計測)と Frame Debugger (レンダリングの順番と内容)を表示します。
f:id:simplestar_tech:20180908221157j:plain

Profiler の Overview にて、大雑把に関数ごとの処理時間を知ることができ
Timeline 表示で他のスレッドの処理の様子も見ることができます。(2017.3以降、かつスクリプトに開始と終了のコードを仕込む必要があります)
実装詳細はこちら
Unity - Scripting API: Profiling.Profiler.BeginThreadProfiling

CustomSampler sampler = CustomSampler.Create("Exec");
Profiler.BeginThreadProfiling("My threads", "My thread 1");
sampler.Begint();
// ... スレッドの処理
sampler.End();
Profiler.EndThreadProfiling();

をスレッドの関数内で記述します。

また Editor で開発中は Deep Profile を有効化してさらに細かい関数ごとのパフォーマンスをチェックすることが可能になります。
Android でも adb shell am start -n [パッケージ名]/[アクティビティ名] -e "unity -deepprofiling" コマンドで Deep Profile を有効化できるようになりました。(2017.3より)
Windows マシンに Android を接続した状態で AndroidPlayer を対象に選べば Development Buildを有効にしたアプリであれば Android 実機上でのパフォーマンスも計測できます。
さらに自身で定義した処理区間を Profiler に名前入りで表示することも可能です。
具体的には以下のコードで

    CustomSampler sampler;
    void Start()
    {
        sampler = CustomSampler.Create("MyCustomSampler");
    }

    void Update()
    {
        sampler.Begin();
        // do something that takes a lot of time
        sampler.End();
    }

Profiling.CustomSampler.Begin - Unity スクリプトリファレンス

・Frame Debugger は Unity の描画処理を 1 ステップずつ確認できるレンダリングにフォーカスした確認ツールです。
・Console ウィンドウ
Debug.Log 関数の内容を表示する。(基本ですね)
Console ウィンドウも対象を選択できる機能があり、Player Logging を有効化して Android Player を選べば Development Build を有効にしたアプリのログを表示することができます。

・Script Debugging
こちらも基本ですが、Visual Studio の UI から Unity にアタッチする操作が可能です。
アタッチできれば、Visual Studio の方で配置したブレークポイントでブレークして変数の中身のウォッチなどのデバッグを行うことが可能です。(Visual Studio での開発からUnity開発に流れてきた人にとってはこれが一番うれしいデバッグ機能ですね)
実機の場合でもこのScript Debugging を行うことが可能ですが、ディフォルト設定だとできないという落とし穴を用意していますので、知らない人はずっと苦しむことになります。
具体的にはPlayer Settings にて Mono か IL2CPP を選ぶ項目にて Mono を選んでいる状態かつ、Unity からのビルド時に Development Build かつ、Script Debugging のチェックボックスにチェックを入れている場合のみ、C# Script Debugging を行うことができます。
Visual Studio のUIにて「Unity にアタッチ」の他に「Unity デバッガーのアタッチ」を選べるはずなので、そこで AndroidPlayer(USB)を選択します。
この三つの条件+Visual Studio のUI操作をすべて満たしていないと絶対に Script Debugging できませんので、実機でのパフォーマンスうんたら言う人は必ずエンジニア全員にこのデバッグ方法を周知してから作業に取り掛かってください(この実機でのスクリプトデバッグ方法の設定をチームメンバーに周知しないプロジェクトはこの世に存在しません。実機でデバッグせずに開発を続けることはあってはならない!)
なお Unity 2018.2 からは IL2CPP でも Script Debugging が可能になりました。ただし、こちら .Net 4.x Equvalement にのみ対応しています。(※初回アタッチだけ時間を要する模様)

あとはプラットフォームごとのツールを使って CPU の利用状況などを見ていきます。
PC/Android は RenderDoc
iOS は Isntruments, OpenGLES Frame DEbugger
Android は Systrace, Snapdragon Profiler, GAPID などなど

一瞬ちらつくなど、対処が難しすぎるバグに対応するときはこうした外部ツールを使い始める場面もあるでしょう。

そのほか Unity Prfiler にて示されているものは Recorder API から取得可能です。
具体的には

Recorder recorder = Recorder.Get("Camera.Render");
float ms = recorder.elapsedNanoseconds * 0.00001f ;// で ms オーダーの処理時間を取得

といったコードで取得できます。

メモリの使用状況やヒープサイズなんかも Profiler API から取れるそうです。

// Manager Memory 使用状況
Profiler.GetMenoHeapSizeLong
Profiler.GetMenoUsedSizeLong
// Unity が確保したメモリの使用状況
Profiler.GetRuntimeMemorySizeLong
Profiler.GetTotalReservedMemoryLong
Profiler.GetTotalUnusedReservedMemoryLong

読み込んだアセット一覧なんかも

Resources.FindObjectsOfTypeAll

を利用することでメモリ上にあるアセットを列挙(※Editor 上では余計なものも列挙するので実機上で行う必要あり)
Texture2D, Meth, AudioClip, AnimationClip 一覧を上記関数で調べられるとのこと

なお、こんなことしなくても、リソースごとにどれくらいメモリを使っているかを示す新しい Memory Profiler が 2018.3 から Package Manager 経由で Alpha Preview できるようになるとのこと
これからもっとメモリのプロファイリングは、取得したい情報が取れるようになっていくとのことです。

最後に示されたのは実機で Profiler のログを残すという手段

Profiler.logFile = Path.Compine(Applicaton.persistentDataPath, "profiler.log");
Profiler.enableBinaryLog = true;
Profile.enabled = true;
// ... 計測対象ループ
Profile.enabled = false;
Profiler.logFile = null;

こうして記録したファイルを実機から取得するには
Android だと adb pull filepath で PC 側にファイルを取得可能
iOS だと info.plist でファイルアクセス許可を追加すると、iTunes のアプリ項目からファイルのダウンロードが可能になります。
そうして手に入れたファイルは Unity Profiler の Load ボタンからファイル選択して読み込み可能です。

さて、ここで大問題!
Unity Profiler は最新の 300 frame しか表示することができないので、それより長い Profiler ログを渡しても全部確認することができません!
そうした問題に対応するようにプラグインを発表者の方は作成されています。
github.com
300フレーム問題で苦しみ出したら、このプラグインを使わせてもらいましょう。

本当に最後は 2018.2 より提供された ResourceManager のための Profler
RM Profiler の話で締めくくられました。

ここまでの話を、再度おさらいするのに、テラシュールブログの方の解説も読むと漏れなく学習できると思います。(いつも感謝です)
tsubakit1.hateblo.jp

■ここから本題
スライドのチェックはここまでにして
本題の実機上で一目でわかるプロファイル情報表示UIを作るための書式確認を始めたいと思います。

と思ったら、発表スライドにて、そのUIツールの実装へのリンクが示されていました。
github.com

導入の仕方:
リポジトリをダウンロードしたら MainLoopProfiling.unitypackage を起動して Prefab と .cs ファイル x4 をインポート
Prefab をシーンに配置すると UI が完成

ソース内容はここまで読めばわかると思いま…いや、わからなかったですね。
実装を詳しく見ていくと Unity 2018.1 から使える PlayerLoop を活用したものであることがわかります。
色々なループの型があるのですが、代表的なものとしてこんなものが定義されています。

namespace UnityEngine.Experimental.PlayerLoop
{
    public struct Update
    {
        public struct ScriptRunBehaviourUpdate

        public struct DirectorUpdate

        public struct ScriptRunDelayedDynamicFrameRate

        public struct ScriptRunDelayedTasks
    }
}

こうした型を指定して、例えば ScriptRunBehaviourUpdate の前後に関数を仕込ませ、開始と終了の間の時間を Time.realtimeSinceStartup の値で計測
これを集計して UI のアンカーポイントをずらしながら色付きのバーを動かしていました。
Android の MultuThreadRendering の仕組みまで考慮して Rendering 時間を計測するコードが書かれており、私が手を入れる部分が無いですね
FPS テキストは 1 秒に一回の頻度で更新するように書かれていました。
Unity 2018.1 以降で開発される方はこの計測器を配置してゲームを開発されるとよろしいのではないでしょうか?

こちら Android (Xperia XZs Android 8.0.0) で Basic Rendler Pipeline の描画シーンをキャプチャした画面です。
f:id:simplestar_tech:20180909180934p:plain

Main Thread はレンダリングに比重を置きすぎかもしれませんね。(ビルド設定では MultiThreadRendering 有効化しているけど、実機が対応できていないのかな?)
ただ 60 FPS 出ているのはさすが

もうすぐ AppleiPhone の新しい発表が待っているし、そろそろ iOS で開発してみたいもんです。
新しい iPhone 買っちゃおうかなぁ
Mac Book Air も買っちゃおうかなぁ