Unityのシーンとかアセットのメモリ上の展開具合とCPUのオーバーヘッドはInsturmentで発見しようって事

Ad


Uniteより。

講演者情報

高橋啓治郎氏

http://www.radiumsoftware.com/index.html

雑談

全てのソフトウェアはユーザがメンタルモデルを構築する事で、実装等を考えなくても使える。
これはひとえにUIやUXによるところが大きい。

とかそういう話。

シーンのオーバーヘッド

コンパイルするとどうなってるか?

まずは実演。簡単なシーン3つ(以下の通り)をコンパイルしてみる。

  • 1とかかれたテクスチャがひっついたボックスと、2とかかれたテクスチャのボックスがならぶシーン
  • 3と書かれたボックスと4とかかれたボックス
  • 4とかかれたボックスと5とかかれたボックス

これをWebPlayer用にコンパイルすると1つのデータと1つのHTMLになる。
これだと中身がわからないので分かるデータにアクセスしてみる。

tempディレクトリ

ビルドをした後、Unityを閉じずにTempディレクトリにアクセスすると以下のようになっている。
Screen Shot 0025-04-15 at 21.01.38

※画像はシーンが2つのものでテストしたのでファイル数は少ない
シーンは3つだったのにコンパイル後のデータは6つ。

  • mainData
  • sharedassets0.asset
  • sharedassets1.asset
  • sharedassets2.asset
  • level0
  • PlayerConnectionConfigFile

この中にはUnityのシーンで使用するデータが羅列されている。
中身はバイナリなので読む事が出来ないが、sharedassetsはbinarytotextで読める様になる。

binarytotextについて

binarytotextはlinux系のコマンドで、human-readableな文字列に変換してくれる。
ざっとみたところいくつかのやり方があるようで、Uniteで実演されてたのはターミナルから変換するやりかただった。

コンパイル済みシーンデータ(sharedassets)の内容

定義されたデータは、シーン間のデータの重複が無い様に以前に参照するだけでデータ内容は定義されない。自分より若いシーン(buildセッティングで順番を定義)で定義済みだった場合はその後のシーンデータには参照のみで、値は定義されない。

binarytotextで開くとデータはid単位で区分けされ、その区分内に使用するプレファブやテクスチャ等のデータが入っている。

ID1で定義されたデータはプリロードデータ。2以降にシーン内のデータが定義されるが、既存の定義済みデータを参照する場合はID1に記述される。

シーンの読み込みと破棄

loadSceneはシーンを読み込んでから破棄する。
重複する可能性があるので読み込んでからでないと破棄していいか分からないから。
よって、2シーンがメモリ上に展開される事になり、かなりメモリを多く読む事になる。

フレーム間の処理はインスツルメンツを使う。
DevelopmentBuildでないと関数のシンボルが外れてInstrumentsでも良くわからない事になる。

Instrumentsの場所はココ
Instruments_boot

シーンを読み込む時のメモリ状況についてはUnityのプロファイラでは負いきれない。
Unityのプロファイラは1フレーム単位でしか管理できないため、フレーム内の状況についてはXcodeを使った方が分かりやすく見える。

InstrumentsのCPUタイムプロファイラ

Instruments

XcodeのツールからInstrumentsを開き、CPU Time Profilerを起動する。
画像のようにウィザードが出現するのでTimeProfilerを選択すればOK。

Instruments_wizard

Instrumentsで関数単位で追う事ができる。何にCPUの何パーセントが使われているかとかが全部出ている。

コールツリーにすると処理の順番がツリー構造で見える。
Instruments_callTree

プロファイルのビルドコンフィグでリリースになっているのをデバッグに変更しておく。これをしておかないとシンボルが切れてしまって何が起こってるのか終えなくなる。

Instruments_view

画像に使っているInstrumentsで試したコードは以下の通り。

https://gist.github.com/ogkslownin/5465729

using UnityEngine;
using System.Collections;

public class MaterialChanger : MonoBehaviour {
  Material mat;
  // Use this for initialization
	IEnumerator Start () {
		while(true){
			StartCoroutine("ChangeColor");

			yield return new WaitForSeconds(5f);

			StopCoroutine("ChangeColor");

			yield return new WaitForSeconds(2f);
				InstantiateMe();

		}
	}
	
	// Update is called once per frame
	IEnumerator ChangeColor () {
		Debug.Log("Changer run");
		int x = 100;
		for(int i = 0; i < x; ++i ){
			x = UnityEngine.Random.Range(1, 1000);
			switch( x % 5 ){
				case 0:
					renderer.material.color = Color.red;
					break;
				case 1:
					renderer.material.color = Color.blue;
					break;
				case 2:
					renderer.material.color = Color.green;
					break;
				case 3:
					renderer.material.color = Color.black;
					break;
				default :
					renderer.material.color = Color.white;
					break;
			}
		yield return null;
		}
	}
	void InstantiateMe () {
		Debug.Log("InstantiateMe run");
		int x = 5;
		for(int i = 0; i < x; ++i ){
			GameObject go = gameObject;
			Instantiate(go,transform.position,transform.rotation);
		}
	}

}

一定時間でマテリアルのカラーを数十回、数百回変更し、さらに時間が経過すると自身を5体生成する。
実際にInstrumentsでは”MaterialChanger_ChangeColor_…”と表示されているので、ああ重いんだなぁと推測できる。

loadSceneAsync等の非同期読み込みがあるけど、前述のとおり、メモリに2シーン展開するのでメイン側でガク付くことが多い。これはメモリまわりのプロファイラでチェックする。記事では省略。

メモリの話

メモリの領域は大きく分けて3つの世界に分割できる。

  • システム
  • メモリマネージャ
  • Mono

システムはOSトカ。メモリマネージャが入ってるところがUnityの領域。MonoはC#のランタイムとか入ってるところでGCが動くけどあまり直接的には手を出せない。


GC.Collect()

これで強制的にGCを発動できるけど動作が保守的で確信を持って参照が無い事が約束されない限りは回収されない。よってここにあまり期待しないほうがいい。

Unityの領域はロードレベル。一度読んだら追加で読まないのが基本。
ユーザレベルではそういうわけにはいかない。
シーン内でメッシュやテクスチャを生成したりするので増減は発生する。

メッシュをnewしたものは自分でdestroyすること。
loadしたものはunloadすること。
こういうのはunity管理対象外。

unloadは4.1以降軽くなった。

Assetbundle

データの構造はWebPlayerでコンパイルしたみたいな構造。
AssetBundleのヘッダと実データ構造体の連番で管理されている。
プリロードテーブル、コンテナマップ(読むべきものを定義)、メインアセット(プロパティ参照用)
ハッシュが一致しないとアセットバンドルをダウンロードできない。

データが他と重複するなら依存性を構築して外部参照させることもできる。
ただし、これをすると参照先のアセットバンドルのデータの並びが変更されたり、先に説明した連続体のうちの若い番号がなくなることによってナンバリングが変わる等が怒らない様にしなければならない。

この対処法として32bitのIDで検索する事が出来る様になる方法もある。
Deterministic option で連番からID管理で検索する方式へスイッチ。
これをやると並びはぐちゃぐちゃになるのでシークに時間がかかるようになる。
iOS device等であればランダムアクセスなのでその限りではない。
DVDロムでゲームを配信する等の場合に限り期を付けるべきこと。

Unityで作るスマートフォン3Dゲーム開発講座 Unity4対応 (SMART GAME DEVELOPER)
C#クックブック 第3版

Unityのシーンとかアセットのメモリ上の展開具合とCPUのオーバーヘッドはInsturmentで発見しようって事」への1件のフィードバック

保永 にコメントする コメントをキャンセル

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です