UnityのProfilerの簡単な使い方

# はじめに
この記事は [クラスター Advent Calendar 2023](https://qiita.com/advent-calendar/2023/cluster) の12/14の分です。
14日目担当のzisupです。

昨日は @kouta_vr さんの「[Tauriでばもきゃと連携する話： Webでもトラッカーの情報を扱いたい!](https://qiita.com/kouta_vr/items/46f897481bd00ffe1665)」でしたね。

# clusterをサクサク快適に動作させたい
clusterの設定メニューにはその他のページにクリエイター向け機能として統計のチェックボックスがあります。
オンにするとパフォーマンス情報が表示され画面右上にFPSやメモリの数値が表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588722/dfaacbd2-5cb8-998d-f28f-7180463eccb6.png)

高いFPSが維持されていればサクサク快適に動作してますが、たまにガクっと下がるときもあります。
アプリケーションで処理落ちしているとき、一体何が原因なのか？
UnityEditorには調べる方法としてProfilerが用意されています。

この記事では
Profilerってきいたことあるけど、よく知らない
知ってるんだけど、使ったことない
みたいな人を対象にして超簡単な使い方を紹介します。

気軽に使っていきましょう。

# プロファイルってなに
CPU/GPUの処理負荷やメモリ負荷を測るためのもの
エンジニアにとっては大きな味方
覚えると実力アップ

## Profilerの使い方
公式ドキュメントを読めば完全に理解できます。

https://docs.unity3d.com/ja/2021.3/Manual/Profiler.html

がたくさんあるので、CpuProfilerの最低限覚えておきたいことを書きます。
空のUnityProjectを作成して簡単に使ってみます。

## Profilerを開く
Window -> Analysys -> Profiler or Profiler(Standalone)
をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588722/0f30d217-7d4a-2579-6856-ff9d88a43e99.png)

個人的にはStandalone版の方がEditorの処理負荷が表示されないので好きです。

:::note warn 
Profiler(StandaloneProcess)について
パッケージの組み合わせで動かない場合がありました。
何かのパッケージ更新して動かなくなったら疑うといいかも。
https://issuetracker.unity3d.com/issues/profiler-standalone-process-freezes-when-cinemachine-is-installed
:::
## マーカーを仕込む
処理負荷を調べたい箇所を Profiler.BeginSample と Profiler.EndSample で囲みます。
BegimSample～EndSampleで囲んだ箇所をマーカーとよく呼ぶので以後、マーカーと呼んでいきます。

```c#
public class Main : MonoBehaviour
{
    void Update()
    {
        Fuga();
        Hoge();
    }

    void Fuga()
    {
    }
    void Hoge()
    {
        Profiler.BeginSample("Hoge");
        Profiler.EndSample();
    }
}
```
プロファイラのスクショ
![キャプチャ 2023_11_27 18_11_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588722/1bb7382b-2207-9466-fba9-1bdc6916947c.png)
Hoge()の呼び出しの処理負荷がヒエラルキーに表示されています。
FugaはProfiler.BeginSample~EndSampleがないので表示されていません。

:::note warn
フレームを跨いでマーカーを仕込むと正しく計測できません。
async/awaitなんかは待ちを跨がないでマーカーを仕込みましょう。
:::

## どこにマーカーを仕込むべきなのか？
重そうに思った箇所があれば積極的に仕込みましょう。
Unityの関数ならScriptReferenceを確認しにいくと重いって書いてることもあります。
メインスレッド以外で仕込んでも計測できます。

例

https://docs.unity3d.com/ja/2021.1/ScriptReference/Texture2D.SetPixel.html

Call Apply to actually upload the changed pixels to the graphics card. Uploading is an expensive operation, so you'll want to change as many pixels as possible between Apply calls.
expensiveと書かれていてコストが高いのがわかります。


### 毎フレーム実行する独自の関数
 Monobehaivourの定義済みの関数(Start/Updateなど)はデフォルトでProfilerに表示されますが、関数内で呼び出す関数まではわからないので表示されません。
 
### 長い関数
数百行あって色んな関数や処理をしていると、どこかで重い処理があるかもしれません。

### new ObjectやCreateXXX
Unityの処理によっては重いことがあります。

例
```c#
public static long NativeCall()
{
    using (var javaObj = new AndroidJavaObject("Hoge"))
    {
        return javaObj.Call<int>("Fuga");
    }
}
```
コード的には綺麗だけど呼び出しのたびnewで３msぐらいかかっていました。
変数にキャッシュするだけで初回だけの負荷にできます。

### ループ系の処理
ループ内の処理一回は軽い処理だったとしても、ループ回数次第になります。
一回0.01msが1000回ループすれば10msです。

### GPUにアクセスしてそうな処理
 TextureやImageに関連する処理は重いことがあります。
 特にデータ取得や設定しているとGPUへのアクセスが発生することで重くなりがちです。

### 型変換が行われてそうなところ
 無駄なGCが起きているかも。
```c#
IList<int> GetList()
{
    List<int> hoge = new List<int>(100);
    for (int i = 0; i < 100; i++)
    {
        hoge.Add(i);
    }
    return hoge.ToArray();
}
```
### 文字列作ったり連結したり
文字列作る時にはGC.Allocが発生していて重いことがあります
Debug.Logは特に重いです。

:::note warn
DevelopmentBuildじゃなくてもDebug.Logは処理が残ります。
可能なら消しましょう。
:::

### Transformの変更
親子構造がある場合、座標移動すると相対位置が変わるので再計算が起きてるかも。

### 階層構造があるものを検索する処理
GetCompnentsInChildrenやGetCompnentsInParent
作った当初は階層構造が浅かったけど知らないうちに深くなっているかも
検索する処理は軽いけど、見つかった数が多いと後処理が問題になるかも

## ヒエラルキーとTimelineどっちでみるべき？
### タイムライン
タイムライン表示で全体が見やすいので、まずはこれで全体を把握しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588722/68ae63a8-67e5-7a1e-f812-39507de5932a.png)

### ヒエラルキー
処理時間を詳細に見やすいので調査するときはこちらを見ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588722/7d4ad20c-298c-1ab9-3eb7-622ba42e00d3.png)


## 最低限覚えたらいいこと
ながながと色々書いてきましたが、気になったらマーカーを仕込んでみましょう。

### Profilerは楽しい
自分のコードで何msかかってるのか、GC.Allocがどれくらいあるのか見るのは楽しいです。
速くするのはもっと楽しいです。

## マーカー仕込んだことが処理負荷にならない？
[Conditional("ENABLE_PROFILER")]によってENABLE_PROFILERが定義されてなければ関数呼び出しがなくなります。
なのでReleaseBuild時には問題無し

# おわりに
明日は @take1108 さんです。
楽しみですね。