95
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

【Unity】Unite 2016「モバイル端末向けのUnityアプリケーションの最適化実践テクニック」翻訳

  • いくつか翻訳が間違っている可能性があるためご了承ください
  • 一部の項目は英語力が及ばす翻訳できませんでした
    • 講演動画が公開されたら追記するかもしれません

講演資料

Profiling

オススメのツール

  • iOS:Instruments
  • Android:VTune, Snapdragon Profiler
  • Unity Editor:タイムライン
  • 5.3以降:Memory Profiler

Instruments

InstrumentsでPlayerLoopを読む

  • BaseBehaviourManager::CommonUpdate
    • UpdateFixedUpdateLateUpdate
  • PhysicsManager::FixedUpdate
    • OnCollision*OnTrigger*
    • Physics2DManager::FixedUpdate
  • DelayedCallManager::Update
  • PlayerRender
    • OnWillRender
  • UI::CanvasManager::WillRenderCanvases
  • EnlightenRuntimeManager::Update

InstrumentsにおけるCoroutineの扱い

  • Coroutineの実行は2つに分割されます
    • StartCoroutineを呼び出した場所
    • DelayedCallManager

Instrumentsにおける検索方法

  • 検索欄にメソッド名を入力します
  • オススメの検索ワード
    • ::BoxBox(_Box
    • String_

5.3以降:Memory Profiler

導入方法

  1. Unityプロジェクトの「Assets」フォルダに「Editor」フォルダを追加
  2. メニューの「Window>MemoryProfilerWindow」を選択
  3. 「Take Snapshot」を選択

Assets

Assetの設定ミス

  • 開発者は人間なのでヒューマンエラーが発生します
  • ミスは開発時間を浪費します
  • ミスを防ぐために設定を自動化することをオススメします

Assetのよくある設定ミス

  • テクスチャサイズがおかしい
  • 圧縮形式がおかしい
  • アバターのリグの設定がおかしい
  • アセットによって設定規則が違うことがある

Assetの設定の自動化

public class AssetAuditExample : AssetPostprocessor
{
    public void OnPreprocessTexture() 
    {
        // …
    }
    public void OnPreprocessModel() 
    {
        // …
    }
}
  • AssetPostprocessorのコールバック関数はAssetのインポート時に呼び出されます
  • OnPreprocess*関数を実装する必要があります
  • assetImporterインスタンスを使用してAssetの設定を変更します
public class ReadOnlyModelPostprocessor : AssetPostprocessor
{
    public void OnPreprocessModel()
    {
        ModelImporter modelImporter = (ModelImporter)assetImporter;
        if(modelImporter.isReadable)
        {
            modelImporter.isReadable = false;
            modelImporter.SaveAndReimport();
        }
    }
}

テクスチャの汎用ルール

  • Read/Writeを無効にする
  • 可能ならミップマップを無効にする
  • テクスチャが圧縮されているかどうかを確認する
  • テクスチャのサイズが大きすぎないように注意する
    • UIのアトラスは2048x2048や1024x1024で問題ない
    • モデルのテクスチャは512x512以下で作成する

モデルの汎用ルール

  • Read/Writeを無効にする
  • キャラクターではないモデルのリグを無効にする
  • メッシュの圧縮を有効にする

オーディオの汎用ルール

  • iOSではMP3の圧縮を有効にする
  • AndroidではVorbisの圧縮を有効にする
  • モバイルゲームではモノラルを使用する
  • 可能な限りビットレートを落とす

Unityにおけるメモリ

Unityにおけるメモリ管理の問題点

  • ヒープ領域が縮小されることはない
  • 一時的なメモリ割り当てが問題になる
  • 60FPSのゲームで1KBの割り当てが毎フレーム行われた場合、1秒で60KBのメモリ割り当てが発生する
  • もし1分ごとにGCが動いた場合ゲームパフォーマンスに影響してしまう
  • 3,600KBのメモリが必要になってしまう

GC Allocを追う

  • UnityのProfilerを開いてGC Allocでソートする
  • もし0Bじゃない項目があれば、可能な限り0Bに近づける
  • ただし、シーンの読み込み中のGC Allocは仕方がない

メモリの安全活用

  • ListHashSetを使い回す
  • stringを避ける
    • StringBuilderを再利用すること
  • デリゲートやラムダ式による匿名関数を避ける

ボックス化を避ける

  • ボックス化は値型を参照型として渡す時に発生します
  • 値が一時的にヒープ領域に割り当てられます
int x = 1;
object y = new object();
y.Equals(x); // ここで「x」がボックス化されてヒープ領域に割り当てられます
  • Dictionaryのキーに列挙型を使用した時にも発生します
enum MyEnum { a, b, c }

// ...

var myDictionary = new Dictionary<MyEnum, object>();
myDictionary.Add(MyEnum.a, new object()); // ここで「MyEnum.a」がボックス化されます
  • IEqualityComparerを使用することで回避可能です

foreachを避ける

  • ループ開始時にEnumeratorが割り当てられてしまいます
  • foreachではなくforを使用すること

UnityのAPIに気をつける

  • 配列を返すAPIは毎回配列を作成してしまう
    • 値が変化していない場合でも
  • 次のコードではTouch[]が大量にメモリに割り当てられます
for ( int i = 0; i < Input.touches.Length; i++ )
{
    Touch touch = Input.touches[i];
    // …
}
  • 下記のように書き換えることでTouch[]のメモリ割り当てを1度に抑えることができます
Touch[] touches = Input.touches;
for ( int i = 0; i < touches.Length; i++ )
{
    Touch touch = touches[i];
    // …
}

CPUパフォーマンスにおけるTips(読み込み周り)

XMLやJSONなどのテキストフォーマット

  • テキストの変換は非常に遅い
  • よくあるJSONパーサは内部でリフレクションを使用しているため遅い
    • Unity 5.3以降であればJsonUtilityを使用することがオススメ
    • JsonUtilityはリフレクションではなくシリアライズを使用しているため速い

XMLやJSONの使用量を減らす

  • 戦略その1:テキストの変換は行わない

    • ScriptableObjectを使用する
    • 頻繁に変更されないマスタデータなどに対して有効
  • 戦略その2:必要な部分のみ変換する

    • テキストの変換が必要な場合は可能な限り小さい範囲で変換する
    • 変換後の結果をキャッシュして使い回す
  • 戦略その3:スレッドを使用する

    • テキストの変換を別スレッドに逃がす
    • ただし、別スレッドではUnity APIが使用できないので注意が必要

巨大なプレハブ

※上手に翻訳できませんでした

Resourcesフォルダ

  • ゲーム開始時にResourcesフォルダ内のすべてのアセットにインデックスが割り振られます
  • これは回避したり遅延させたりすることができません
  • Resourcesフォルダのアセットをアセットバンドルに逃がして解決します

CPUパフォーマンスにおけるTips(ランタイム)

Material/Animator/Shaderのプロパティ

  • MaterialやAnimator、Shaderのプロパティには名前でアクセスします
  • 内部的には指定された名前をint型のハッシュ値に置き換えます
  • 次のコードのような処理を書いてはいけません
material.SetColor(_Color, Color.white);
animator.SetTrigger(attack);
  • 最初にハッシュ値をキャッシュしてそれを利用します
static readonly int material_Color = Shader.PropertyToID(_Color);
static readonly int anim_Attack = Animator.StringToHash(attack);
material.SetColor(material_Color, Color.white);
animator.SetTrigger(anim_Attack);

string型のボックス化

  • 正規表現やstring.StartsWithstring.EndWithは遅い
  • Instrumentsでボックス化が発生している場所を検索可能です
    • ::Box_Boxで検索
    • String_で検索

Unity UI

※上手に翻訳できませんでした

Trampolines

はじめに

  • 必要なときにだけこれらの技術を適用します

Unityのコールバックがどのように呼び出されているか

  • コンポーネントやそれらに紐付くコールバック関数は、内部的にはC++でLinked Listで管理されています
    • UpdateLateUpdateなど
  • Linked Listを反復処理して各コールバック関数を呼び出しています
  • これら小さなオーバーヘッドになります
  • コールバック関数の数が非常に多くなると、オーバーヘッドも無視できなくなります

UpdateManagerを作成してコールバックを置き換える

public class UpdateManager : MonoBehaviour
{
    public static UpdateManager Instance { get; set; }

    void Awake() { Instance = this; }

    public UnityEvent OnUpdate = new UnityEvent();

    void Update() 
    {
        OnUpdate.Invoke();
    }
}

メリット

  • Trampolineのオーバーヘッドが消去できます
  • 登録したコールバックは解除することができます
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
95
Help us understand the problem. What are the problem?