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

  • 76
    Like
  • 0
    Comment
More than 1 year has passed since last update.
  • いくつか翻訳が間違っている可能性があるためご了承ください
  • 一部の項目は英語力が及ばす翻訳できませんでした
    • 講演動画が公開されたら追記するかもしれません

講演資料

http://japan.unity3d.com/unite/unite2016/files/DAY1_1330_Room1_HarknessDundore_Long_Big.pdf

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のオーバーヘッドが消去できます
  • 登録したコールバックは解除することができます