- いくつか翻訳が間違っている可能性があるためご了承ください
- 一部の項目は英語力が及ばす翻訳できませんでした
- 講演動画が公開されたら追記するかもしれません
講演資料
Profiling
オススメのツール
- iOS:Instruments
- Android:VTune, Snapdragon Profiler
- Unity Editor:タイムライン
- 5.3以降:Memory Profiler
Instruments
- 無料でXcodeに内蔵されています
- IL2CPPビルドでも動作します
- モバイルCPUのプロファイリングに最適なツールです
- 起動時間のプロファイリングに最適なツールです
- Instrumentsの使い方
http://blogs.unity3d.com/2016/02/01/profiling-withinstruments/
InstrumentsでPlayerLoopを読む
-
BaseBehaviourManager::CommonUpdate
-
Update
、FixedUpdate
、LateUpdate
-
-
PhysicsManager::FixedUpdate
-
OnCollision*
、OnTrigger*
Physics2DManager::FixedUpdate
-
DelayedCallManager::Update
-
PlayerRender
OnWillRender
UI::CanvasManager::WillRenderCanvases
EnlightenRuntimeManager::Update
InstrumentsにおけるCoroutineの扱い
- Coroutineの実行は2つに分割されます
-
StartCoroutine
を呼び出した場所 DelayedCallManager
-
Instrumentsにおける検索方法
- 検索欄にメソッド名を入力します
- オススメの検索ワード
-
::Box
、Box(
、_Box
String_
-
5.3以降:Memory Profiler
- BitbucketからソースコードをDL可能です
https://bitbucket.org/Unity-Technologies/memoryprofiler - 重複しているテクスチャが調査できます
導入方法
- Unityプロジェクトの「Assets」フォルダに「Editor」フォルダを追加
- メニューの「Window>MemoryProfilerWindow」を選択
- 「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は仕方がない
メモリの安全活用
-
List
やHashSet
を使い回す - 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
はリフレクションではなくシリアライズを使用しているため速い
- Unity 5.3以降であれば
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.StartsWith
、string.EndWith
は遅い - Instrumentsでボックス化が発生している場所を検索可能です
-
::Box
や_Box
で検索 -
String_
で検索
-
Unity UI
※上手に翻訳できませんでした
Trampolines
はじめに
- 必要なときにだけこれらの技術を適用します
Unityのコールバックがどのように呼び出されているか
- コンポーネントやそれらに紐付くコールバック関数は、内部的にはC++でLinked Listで管理されています
-
Update
、LateUpdate
など
-
- 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のオーバーヘッドが消去できます
- 登録したコールバックは解除することができます