Edited at

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

More than 3 years have 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のオーバーヘッドが消去できます

  • 登録したコールバックは解除することができます