20
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

KLab EngineerAdvent Calendar 2019

Day 25

UnityのInstantiateについて今更掘り下げてみた

Posted at

はじめに

Unityは2019までバージョンが出ています。
僕がUnityを使い始めたのはまだ標準での2DのUI機能が乏しかったUnity4.xの時代でした。
あの頃から振り返るととてもいい時代になったなと思います。

今更ながらInstantiateについて掘り下げてみたいと思います。

環境

PC: MacBookPro(2015), Windows10
ツール: Untiy2018.3.4f

Instantiateとは

Unity.Objectを複製する関数。
GameObjectの複製でよく使われているイメージがあります。

重いと言われてるイメージがある

Google先生に聞いてみると
image.png
やはり候補に上がっている。

サクッと適当なテストコードで重いのか調べてみる

SampleUI.cs
// シンプルに「Start, Updateを持ったComponent」を一つアタッチされたPrefab
public class SampleUI: MonoBehabiour
{
    void Start() {}
    void Update() {}
}

以下、n回生成するコード

SampleCreate.cs
public class SampleCreate: MonoBehabiour
{
    [SerializeField] int count;
    [SerializeField] SampleUI samplePrefab;

    void Start()
    {
        // ここから計測し、
        for(var i = 0; i < count; i++)
        {
            Instantiate(samplePrefab);
        }
        // ここまでを結果とする。
    }
}

上記をそれぞれ10回ずつ結果をとり、実行にかかった時間の平均値をおおよその値にした結果が以下。

生成数 実行時間
10 3.5ms
1000 360ms

上記はInstantiateだけでかかった時間のみです。
この結果だけで見るとそんなに重くないように思います。
でも実際開発していくと重いと感じることはある。何故?

本当に重いのはInstantiateなのか

Unityにはイベント関数の実行順があります。
PrefabをInstantiateした場合、Prefabに紐づくイベント関数は生成した同一フレームあるいは次フレームで実行されます。
Instantiateが重いと感じる時にこれが原因で重くなっている可能性があります。
実際に以下のコードを各所に埋め込み、確認しました。

テスト.cs
Debug.Log($"frame: {Time.frameCount}, time: {Time.realtimeSinceStartup}");

それぞれ、以下の結果になります。

生成タイミング Awake(OnEnable) Start Update
Start 生成後すぐ 1フレーム目 1フレーム目
Update 生成後すぐ 1フレーム目 2フレーム目
LateUpdate 生成後すぐ 1フレーム目 2フレーム目

実行箇所にもよりますが生成後に上記のイベント関数が実行されていました。
もしPrefabにGraphic(RawImage, Image, Text etc...)が含まれている場合、実行タイミングに合わせてレンダリング処理が実行されます。

問題例

image.png

スクロールビューで初回で全生成あるいは画面領域外まで生成するケース。
上記は1000件のセル情報を生成している。

image.png
プロファイル結果だとこんな感じ。

※画面表示外は生成しない、共通のインスタンスは足りる分は流用、足りない分は生成などの実装工夫で回避することはできる。
※この例は「スクロールビューを改善する」ためのものではなく、「スクロールビューでインスタンスを全生成した時に重いという例」として上げています。

対策

予めPrefabを非アクティブにしておく

そうすることで生成後のコストはInstantiateのみで済む。
ただし開発していく上でこの手段が現実的なのかは疑問は残る。
何故現実的ではないと思うのか。

  • プレハブを編集する毎にアクティブのオン/オフを切り替えなければならない。
    • 人の手で行われる作業なので設定漏れなど発生する恐れがあるため。
  • 制作側だけで判断が難しい。
    • どういう基準でオン/オフにするのか、基本的にはオフのままにするのか、など。
    • このあたりは意識しなくてもいい作りであるのが個人的には健全。

AwakeでGameObjectを非アクティブにする

例えば

SampleCreate.cs
var instance = Instantiate(prefab);
// ここで非アクティブにする
instance.SetActive(false);

上記のように生成後すぐに非アクティブにする場合、Awakeの他にOnEnableも実行される。
これを回避する場合、Instance側のAwakeで非アクティブにすることでOnEnableの実行も回避することができる。

SampleUI.cs
public class SampleUI: MonoBehabiour
{
    void Awake()
    {
        gameObject.SetActive(false);
    }
    void OnEnable()
    {
        // 複製すぐは実行されない
        Debug.Log("OnEnable");
    }
    void Start() {}
    void Update() {}
}

問題例で活用する場合は画面領域外でオフにすることで生成コストを下げることができる。

検証

試しに全オフにする

SampleUI.cs
private void Awake()
{
    gameObject.SetActive(false);
}

結果、さっきの結果と比べて半分近くになる。
image.png

セルは生成されている。
image.png

画面領域内だけを描画する

Scroll.cs

for(var i = 0; i < count; i++)
{
    var instance = Instantiate(samplePrefab, root);
    // 一旦画面では6個までは領域内なので雑に先頭6個だけ表示する、というロジックでテスト
    // AwakeはInstantiate実行後即呼ばれるのでその後にアクティブにしても正常に呼び出される。
    if (i < 6) {
        instance.SetActive(true);
    }
}

結果は以下の通り
image.png

先頭の6個だけアクティブ状態。
image.png

まとめ

  • Instantiateが重いと感じる場合、どういう負荷がかかっているかよく調べたほうがいい。
    • 実は思いもよらぬところで負荷がかかっている可能性がある。
  • Instantiate単体だけでもそこそこ重い
    • Instantiateが本当に重いですか?という指摘の記事で軽いという結論を出したいわけではないので念の為の補足。
  • イベント関数の実行順は改めて理解を深められてよかった。
    • Instantiate後の挙動はどこにも書かれてない(と思う)のでしっかり把握するのは大切だなと思った。
    • 今回Editorでの計測で実機での順序は確認できていません。
  • 今回具体的な対策のための記事ではないということ。
    • こういう問題が起きているかもしれないので調べる、どうしたら解決できるのかは改めて各々で考えてもらえたらなと思います。

最後に

Instantiateが重いという話は調べると出てくるが何故重いのか、という取り上げ方の記事はあまり見かけないなと思っています。
だからこそ上げてみようと思いましたが、実はこういう理由なのでは、こういうのもあるよということがあればコメントでご指摘いただければと思います。

おまけ

test.cs
    IEnumerator Start()
    {
        yield return null;
        Debug.Log($"Start {Time.frameCount}");
    }
    void Update()
    {
        if (Time.frameCount == 2) {
            Debug.Log($"Update {Time.frameCount}");
        }
    }

結果…
image.png

イベント関数の処理順を見てみると…
image.png
上記になっているのでそりゃそうかという。

20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?