YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

【Unity】GameObjectがActiveの場合にSetActiveした時の負荷は?

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / X )です。
この間、仕事中にUnityでGameObjectがActiveの場合にSetActiveをしてしまうコードを書いてしまっていました。
そこで、プルリクにて負荷をできるだけ減らしたいので辞めて欲しいと言われました。
実際のところ負荷はどの程度なのか気になったので、今回は負荷検証をしていきたいと思います。

目次


前提


Unity2022.3.7f1にて検証
DeepProfileをつけた状態で
1万個キューブを作成しかかる時間を測定しました。

結論


まず、ぐだぐだと書くのもなんなのでまずは結論を書いておきます。
フラグを立てて自前で処理する場合は確かに自身に対する処理速度は早くなる可能性があるが
結局ifの処理を挟むということはチェックが必ず必要ということでもあるのでデメリットも有る。
とはいえ、ActiveToActiveはぶっちゃけ気にする程度の処理負荷は出ない。
というのが私の見解です。

具体的にどのような負荷検証をしたのか


以下のようなシーンを用意。

スポーンするキューブはこれ。

スポーンするオブジェクトにアタッチされたコンポーネントはこれ。

Canvasに以下のようなスクリプトをアタッチ

using System.Collections.Generic;
using UnityEngine;

public class ActivateScript : MonoBehaviour
{
    [SerializeField] private GameObject cube;

    private List<GameObject> objects = new ();
    
    // Start is called before the first frame update
    void Start()
    {
        for (int i = 0; i < 10000; i++)
        {
            objects.Add(Instantiate(cube));
        }
    }

    public void SetActivate()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        foreach (var item in objects)
        {
            item.SetActive(true);
        }
        sw.Stop();
        Debug.Log(sw.Elapsed);
    }

    public void SetDeactivate()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        foreach (var item in objects)
        {
            item.SetActive(false);
        }
        sw.Stop();
        Debug.Log(sw.Elapsed);
    }
}

スポーンするオブジェクトにはこのようなコードをアタッチ

using UnityEngine;

public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Start");
    }

    private void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    private void OnDisable()
    {
        Debug.Log("OnDisable");
    }

動作確認


まずは動作確認がてら、全オブジェクトをアクティブ、非アクティブを切り替えてみる。

まずはアクティブ状態化から非アクティブ化
OnEnableでのログも走るし、普通に負荷があると思われる。

まぁ1秒以上かかる

ちなみに1000ミリ秒1秒なので、60fpsで動作させるためには約16ms以内に終わらす必要がある。

非アクティブ状態からアクティブ化

こちらも結構な時間がかかる。

いざ検証。


Deep ProfileをOnにした状態で、エディタの検証をしてみる。

Active To Active

確かに負荷がかかる。
1万個のオブジェクトを切り替えて約6msだろうか。

(ちなみにOnEnableは走らなかった)

Diaciteve To Deactive

なぜかDivacitiveの方がかかる6ms~7ms

実際にDeepProflie上はどうなのかと見てみると0.4msとすごく少ない時間になっている。

どうやらDeepProflie起動中はStopwatchでは純粋な時間を測れるわけでは無いようだ。

(ちなみにOnDisableは走らなかった)

疑問1. エディタ上では遅いだけで、実機ビルドでは遅くないのでは説


他の方の記事を見たが、実機の速度は測られていない。
Unityはビルド時に最適化されGCが走らなくなる処理などがある。
例えばGetComponentなどがそうだ。

これもそうなのではないかという疑問が湧いてきた。

実際にDevelopmentBuildでProfile有りでの検証を行った。

すると

ActiveToActive

かなり速度が違う!!
やはり、実機のほうが処理時間が大幅に早い。

2msほどになった。
先程は6msだったので大きな差だ。

Diaciteve To Deactive

疑問2.activeSelfで事前チェックをすると早くなるのか?


            if (!item.activeSelf)
            {
                item.SetActive(true);
            }

などと書いてみてチェックをしてみた。

私の予想ではエディタ上では書いたほうが早いがデベロップメントビルドは変わらない説

なぜか遅くなった

どうやら調べてみると

object.wbarrier_conc()

という処理に時間がかかっている。

どうやらこの処理はメモリ監視の処理らしい。
DeepProfileをつけた状態ではメモリ監視の処理が余分に入ってこっちのほうが遅くなってしまうようだ。
DeepProfileを上ではソレ以外の明示的に処理が減っているように見えたので、少しは早くなっているかもしれないが、
そもそもactiveSelfにアクセスする前のインスタンスへのアクセス処理が見受けられた。
これならば自身でBoolフラグを作ったほうがGameObjectに対するアクセスをせずに済むので若干activeSelfをみるよりは早いかもしれない。

ちなみに実機の方で見てみたが、activeSelfなしと大した処理の差は見受けられなかった。

疑問3.10個子供を持つキューブを1000個作った場合は1万個キューブを切り替えるときとどっちが負荷があるのか


私の予測では命令処理は減るはずだから軽くなるはずだが……?

正解。

アクティブ化、非アクティブ化どちらもステートが変更済みの場合は命令処理が減るので早くなるようだ。

疑問4. 3D Objectではなく、2D上のObjectだとどうか?


Canvas上のオブジェクトは再描画処理が走ってしまうなどはあるのか?
気になった。

→特に無かった。

若干キューブと比べて処理速度は違ったが、誤差の範囲に思えた。

Canvas自体の方を切り替えても同じだった。(インスタンス化自体が重かったが……)

まとめ


最初の結論に戻るが
フラグを立てて自前で処理する場合は確かに自身に対する処理速度は早くなる可能性があるが
結局ifの処理を挟むということはチェックが必ず必要ということでもあるのでデメリットも有る。
とはいえ、ActiveToActiveはぶっちゃけ気にする程度の処理負荷は出ない。

1万個のオブジェクトを操作するなら気をつける必要性はあるかもしれないが、
そこまで行くとECSとJobSystemなどを使うことになるだろうと思うので
ぶっちゃけUnityではActiveToActiveは気になる処理負荷にはならないというのが私の結論となった。