アプリケーションのパフォーマンスを最適化するにあたって、重要な考慮事項のひとつは、メモリの割り当て (アロケーション) です。このページでは、Unity のネイティブメモリアロケーターのタイプに関する情報を提供し、アロケーターをカスタマイズしてパフォーマンスを向上させられるケースについて説明します。Unity では、アロケーターについてある程度理解しておくことをお勧めします。
アロケーターのタイプとそのデフォルト値の完全なリファレンスは、アロケーターのカスタマイズ で参照できます。
ノート: 全てのプラットフォームがこの機能をサポートしているわけではありません。詳細は プラットフォーム特有の情報 のドキュメントを参照してください。
アプリケーションは、メモリアロケーターを使用して、パフォーマンスと使用可能なメモリ空間とのバランスを取ります。アプリケーションのメモリの空き容量が多い場合は、シーンやフレームをロードするにあたって、より高速でメモリ負荷の高いアロケーターが適しているかもしれません。しかし、アプリケーションのメモリが限られている場合は、低速なアロケーターを使用してでも、メモリを効率的に使用しなければなりません。様々なプロジェクトで最高のパフォーマンスを得るために、各アプリケーションのサイズと要件に合わせて、Unity のアロケーターをカスタマイズすることができます。
Unity には 5 つのタイプのアロケーターがあります。タイプによって、割り当てをメモリブロックに合わせるアルゴリズムが異なるため、適する割り当てもタイプごとに異なります。通常、それぞれの割り当て間の重要な違いは、永続性あるいは割り当ての寿命であり、これによって、割り当てがどこに割り振られるかが決まります。例えば、長寿命の (永続的な) 割り当てはヒープアロケーターとバケットアロケーターに、短寿命の割り当てはスレッドセーフリニアアロケーターと TLS アロケーターに割り振られます。
以下の表は、各アロケータータイプのアルゴリズムと用途をまとめたものです。
アロケータータイプ | アルゴリズム | 用途 |
---|---|---|
動的ヒープ | 2 レベル分離適合 (TLSF) | • メイン (Main) アロケーター • Gfx アロケーター • Typetree アロケーター • ファイルキャッシュ (File Cache) アロケーター • プロファイラー (Profiler) アロケーター • エディタープロファイラー (Editor Profiler) アロケーター (エディター上のみ) |
バケット | 固定サイズのロックフリーアロケーター |
• メイン (Main) アロケーター • Gfx アロケーター • Typetree アロケーター • ファイルキャッシュ (File Cache) アロケーター |
デュアルスレッド | サイズとスレッド ID に基づいて割り当てをリダイレクトする | • メイン (Main) アロケーター • Gfx アロケーター • Typetree アロケーター • ファイルキャッシュ (File Cache) アロケーター |
スレッドローカルストレージ (TLS) スタック | LIFO スタック | 一時割り当て |
スレッドセーフリニア | ラウンドロビン FIFO | ジョブへのデータ渡し用のバッファ |
ノート: このドキュメントに含まれる例は、プレイヤーまたはエディターの終了時にログに書き込まれるメモリ使用量レポートを使用しています。ログファイルを見つけるには、ログファイルのページ の説明に従ってください。
このセクションでは、動的ヒープ、バケット、およびデュアルスレッドアロケーターの機能とカスタマイズ例を紹介します。
デュアルスレッドアロケーター は、動的 アロケーターと バケット アロケーターを組み合わせるラッパーです。具体的には、以下を組み合わせます。
2 つの動的ヒープアロケーター: メインスレッド用のロックフリーのアロケーターと、他の全てのスレッドによって共有される、割り当て時と割り当て解放時にロックされるアロケーター。Unity は、これらのアロケーターを、バケットアロケーターには大きすぎる割り当てに使用します。 動的ヒープアロケーターはメモリブロックを使用します。ブロックの半分以上の大きさの割り当ては、動的ヒープアロケーターではなく、仮想メモリシステムで行われます。
小さな割り当てのためのバケットアロケーター。 バケットアロケーターが一杯になると、割り当てが動的ヒープアロケーターに波及します。
主要なヒープアロケーターは動的ヒープアロケーターです。これは、2 レベル分離適合 (TLSF) アルゴリズムをメモリのブロックに適用します。
各プラットフォームにはデフォルトのブロックサイズがあり、これは カスタマイズ可能です。割り当ては半ブロックより小さい必要があります。割り当てが半ブロック以上である場合、動的ヒープアロケーターには大きすぎるため、代わりに Unity は、仮想メモリ API を使用して割り当てを行います。
動的ヒープアロケーターの使用量レポートの例:
[ALLOC_DEFAULT_MAIN]
Peak usage frame count: [16.0 MB-32.0 MB]: 497 frames, [32.0 MB-64.0 MB]: 1 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 54.2 MB
Peak Large allocation bytes 40.2 MB
この例では、TLSF ブロックサイズが 16 MB に設定されており、Unity は 2 つのブロックを割り当てました。アロケーターのピーク使用量は 54.2 MB でした。この 52.4 MB のうち、40.2 MB は TLSF ブロックには割り当てられておらず、代わりに仮想メモリにフォールバックしました。ほとんどのフレームでは、割り当てられたメモリが 16 - 32 MB でしたが、1 つのフレーム (おそらくローディングフレーム) では、ピークで 32 - 64 MB に達しました。
ブロックサイズを大きくすれば、大きな割り当てが仮想メモリにフォールバックせずに動的ヒープに留まることになります。しかし、そのブロックサイズがメモリの浪費につながる可能性があります。なぜなら、ブロックが完全に使用されない可能性があるためです。
Typetree アロケーターやキャッシュアロケーターを使用しないようにするには、それらのサイズを 0 に設定します。Typetree やキャッシュを使用するはずだった割り当てが、代わりにメインアロケーターにフォールバックするようになります。これにより、断片化がより多く発生する可能性がありますが、これらのアロケーターのメモリブロックのメモリが節約されます。
バケットアロケーターは、小さな割り当てを行う高速なロックフリーアロケーターです。通常、バケットアロケーターは、ヒープアロケーターに行く前に、小さな割り当てを高速化するための第一ステップとして使用されます。
このアロケーターは、割り当て用にメモリのブロックを予約します。各ブロックが 16 KB の サブセクション に分割されます。これは設定調整可能ではなく、ユーザーインターフェースにも表示されません。各サブセクションが 割り当て (アロケーション) に分割されます。割り当てのサイズは、設定された固定サイズの倍数で、粒度 と呼ばれます。
以下の設定例は、割り当て用のブロックを予約するプロセスを示しています。
この設定では、合計 ブロック サイズ (Bucket Allocator Block Size) が 4 MB で、割り当ての粒度 (Bucket Allocator Granularity) が 16 B です。最初の割り当てが 16 B、2番目が 32 B (2*16)、次が 48 B、64 B、80 B、96 B、112 B、128 B で、合計 8 つのバケット (Bucket Allocator BucketCount) になります。
各サブセクションが、それぞれ異なる数のバケットを含みます。サブセクションのバケット数を計算するには、サブセクションのサイズ (16 KB) を粒度のサイズで割ります。例えば、次のようになります。
バケットアロケーターは、開発ビルドとリリースビルドで異なる使用量レポートを作成します。これは、開発ビルドでは各割り当てに 40 B の追加ヘッダーがあるためです。以下の図は、16 B と 64 B の割り当てに関して、開発ビルドとリリースビルドの違いを示したものです。
また、4 MB のうち 2 MB を割り当てただけで一杯とレポートされる理由も、このヘッダーにあります。
[ALLOC_BUCKET]
Large Block size 4.0 MB
Used Block count 1
Peak Allocated bytes 2.0 MB
Failed Allocations. Bucket layout:
16B: 64 Subsections = 18724 buckets. Failed count: 3889
32B: 17 Subsections = 3868 buckets. Failed count: 169583
48B: 31 Subsections = 5771 buckets. Failed count: 39674
64B: 28 Subsections = 4411 buckets. Failed count: 9981
80B: 17 Subsections = 2321 buckets. Failed count: 14299
96B: 6 Subsections = 722 buckets. Failed count: 9384
112B: 44 Subsections = 4742 buckets. Failed count: 5909
128B: 49 Subsections = 4778 buckets. Failed count: 8715
同じプロジェクトのリリースビルドでは、アロケーターのブロックサイズで十分です。
[ALLOC_BUCKET]
Large Block size 4.0 MB
Used Block count 1
Peak Allocated bytes 3.3 MB
バケットアロケーターが一杯になると、割り当てが他のアロケーターにフォールバックします。使用量レポートには、失敗した割り当ての数も含めた使用統計が表示されます。直線的に増加する失敗カウントがレポートに表示される場合、ロードではなくフレームの計算時に割り当ての失敗が発生している可能性が高いことを意味します。フォールバック割り当ては、シーンのロードでは問題ありませんが、フレーム計算時に発生するとパフォーマンスに影響を与える可能性があります。
このようなフォールバック割り当てを防ぐには、ブロックサイズを大きくし、新しいブロックサイズを、シーンのロードのピーク使用量ではなくフレームのピーク使用量に一致するように制限します。これにより、“ブロックが非常に大きくなって、そのために (後にランタイムで使用できない) メモリが大量に確保される” という状況を回避できます。
ヒント: プロファイラーアロケーターは、バケットアロケーターのインスタンスを共有します。この共有インスタンスは Shared Profiler Bucket Allocator でカスタマイズできます。
デュアルスレッドアロケーターは、“小さな割り当てのための共有バケットアロケーター” と、“動的ヒープアロケーターの 2 つのインスタンス (メインスレッド用のロックフリーアロケーターと、他の全てのスレッドで共有されるが割り当て時と解放時にロックされるアロケーター)” をラップします。
2 つの動的ヒープアロケーターのブロックサイズはカスタマイズできます (以下参照)。
使用量レポートには、アロケーターの 3 つ全ての部分の情報が含まれます。以下はその一例です。
[ALLOC_DEFAULT] Dual Thread Allocator
Peak main deferred allocation count 135
[ALLOC_BUCKET]
Large Block size 4.0 MB
Used Block count 1
Peak Allocated bytes 3.3 MB
[ALLOC_DEFAULT_MAIN]
Peak usage frame count: [16.0 MB-32.0 MB]: 8283 frames, [32.0 MB-64.0 MB]: 1 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 53.3 MB
Peak Large allocation bytes 40.2 MB
[ALLOC_DEFAULT_THREAD]
Peak usage frame count: [64.0 MB-128.0 MB]: 8284 frames
Requested Block Size 16.0 MB
Peak Block count 2
Peak Allocated memory 78.3 MB
Peak Large allocation bytes 47.3 MB
ノート: Peak main deferred allocation count は、削除キュー内の項目数です。メインスレッドは、その行った全ての割り当てを必ず削除しなければなりません。他のスレッドが割り当てを削除した場合、その割り当てがキューに追加されます。その割り当ては、メインスレッドによって削除されるためのキューで待機します。そして、延期された割り当てとしてカウントされます。
このセクションでは、スレッドローカルストレージ (TLS) アロケーターとスレッドセーフリニアアロケーターの、機能およびカスタマイズ例を紹介します。
Unity には、デュアルスレッドアロケーターの外側で動作する、以下の 2 つのアロケーターがあります。
スレッドローカルストレージ (TLS): 高速な一時的割り当てのためのスタックベースのアロケーターです。オーバーヘッドがほとんどないため、最も高速なアロケーターです。また、断片化も防ぎます。LIFO (Last In, First Out、後入れ先出し) ベースです。
スレッドセーフリニア: FIFO (First In, First Out、先入れ先出し) 方式のラウンドロビンアロケーターです。一時的なジョブ割り当てで、ワーカースレッド間で短寿命なメモリを渡すために使用されます。
一時割り当て用に、各スレッドが独自の高速なスタックアロケーターを使用します。この割り当ては非常に高速で、寿命が 1 フレーム未満です。
一時アロケーターのデフォルトのブロックサイズは、プラットフォームで 4 MB、Unity エディターで 16 MB です。これらの値はカスタマイズ可能です。
ノート: アロケーターの使用量が設定されたブロックサイズを超えると、Unity はブロックサイズを拡張します。この拡張の限度は、元のサイズの 2 倍です。
スレッドのスタックアロケーターが一杯になると、割り当てが スレッドセーフリニアジョブアロケーター にフォールバックします。1 フレームに 1 - 10 個、あるいはロード中に数百個といった、少数のオーバーフローアロケーションは問題ありません。ただし、フレームごとに数が増加する場合は、ブロックサイズを大きくすることが可能です。
使用量レポートの情報は、アプリケーションに適したブロックサイズを選択するにあたって役立ちます。例えば、以下のメインスレッドの使用量レポートでは、ロードのピークは 2.7 MB ですが、残りのフレームでは 64 KB 未満です。ブロックサイズを 4 MB から 64 KB に縮小し、ローディングフレームから割り当てを “溢れさせる” ことが可能です。
[ALLOC_TEMP_TLS] TLS Allocator
StackAllocators :
[ALLOC_TEMP_MAIN]
Peak usage frame count: [16.0 KB-32.0 KB]: 802 frames, [32.0 KB-64.0 KB]: 424 frames, [2.0 MB-4.0 MB]: 1 frames
Initial Block Size 4.0 MB
Current Block Size 4.0 MB
Peak Allocated Bytes 2.7 MB
Overflow Count 0
[ALLOC_TEMP_Job.Worker 18]
この 2 番目の例では、ワーカースレッドは大きな一時割り当てには使用されていません。メモリを節約するために、ワーカーのブロックサイズを 32 KB に縮小できます。これは特に、各ワーカースレッドが独自のスタックを持つ、マルチコアマシンで役立ちます。
[ALLOC_TEMP_Job.Worker 14]
Initial Block Size 256.0 KB
Current Block Size 256.0 KB
Peak Allocated Bytes 18.6 KB
Overflow Count 0
Unity のワーカースレッドは、FIFO (First-In, First-Out、先入れ先出し) 方式のラウンドロビンアルゴリズムを使用して、ジョブ用のワークバッファの高速でロックフリーな割り当てを行います。ジョブは終了するとバッファを破棄します。
このアロケーターは、メモリのブロックを確保し、そのブロック内でメモリをリニアに割り当てます。使用可能なブロックはプールに保持されます。1 つのブロックが一杯になると、アロケーターがプールから新しいブロックをフェッチします。ブロック内のメモリが不要になるとアロケーターがそのブロックをクリアし、ブロックは使用可能なブロックのプールに戻ります。割り当てを素早くクリアしてブロックを再び利用可能にし、ジョブが数フレームよりも長く割り当てられたままにならないようにすることが重要です。
ブロックサイズはカスタマイズ可能です。アロケーターは必要に応じて最大 64 ブロックまでの割り当てを行います。
全てのブロックが使用中の場合、または割り当てがブロックに対して大きすぎる場合、ジョブアロケーターより大幅に低速なメインヒープアロケーターに割り当てがフォールバックします。1 フレームに 1 - 10 個、あるいは数百個の、少数のオーバーフロー割り当ては、(特にロード中は) 問題ありません。フレームごとにオーバーフローの数が増加する場合は、ブロックサイズを大きくしてフォールバック割り当てを回避できます。ただし、(例えばシーンのローディングなどのイベントでのピーク使用量に合わせて) ブロックサイズを大きくしすぎると、再生中に多くのメモリが使用できない状態になる可能性があります。
例:
[ALLOC_TEMP_JOB_4_FRAMES (JobTemp)]
Initial Block Size 0.5 MB
Used Block Count 64
Overflow Count (too large) 0
Overflow Count (full) 50408
この使用量レポートの例では、アプリケーションに必要なジョブメモリに対して 0.5 MB というブロックサイズは小さすぎたため、アロケーターが一杯になり、そのために多数の割り当てがオーバーフローしました。
ビルドのフレームオーバーフローが十分かどうかを確認するには、ビルドを短時間実行し、続けて長時間実行してください。オーバーフローのカウントが安定していれば、そのオーバーフローはロード中に発生するハイウォーターマーク (最高水準) です。長時間の実行でオーバーフローのカウントが増加する場合は、ビルドがフレーム単位のオーバーフローを処理しています。どちらの場合も、ブロックサイズを大きくしてオーバーフローを減らすことができますが、フレーム単位のオーバーフローと比べてロード中のオーバーフローは重大度の低いものです。
アロケーターの設定をカスタマイズするには、以下のいずれかを行ってください。
-memorysetup-main-allocator-block-size=<new_value>
を使用します。アロケーターのパラメーターの名前とデフォルト値:
アロケーター | 説明 | パラメーター名 | デフォルト値 | ||
---|---|---|---|---|---|
Main Allocators | Unity がほとんどの割り当てに使用するアロケーターです。 | ||||
Main Allocator | Unity がほとんどの割り当てに使用する主要なアロケーターです。 | ||||
Main Thread Block Size | メインスレッドアロケーター専用のブロックサイズです。 | memorysetup-main-allocator-block-size |
16777216 |
||
Shared Thread Block Size | 共有スレッドアロケーターのブロックサイズです。 | memorysetup-thread-allocator-block-size |
16777216 |
||
Gfx Allocator | Unity が Gfx システム関連の CPU の割り当てに使用するアロケーターです。 | ||||
Main Thread Block Size | メインスレッド Gfx アロケーター専用のブロックサイズです。 | memorysetup-gfx-main-allocator-block-size |
16777216 |
||
Shared Thread Block Size | 共有スレッド Gfx アロケーターのブロックサイズです。 | memorysetup-gfx-thread-allocator-block-size |
16777216 |
||
Other Allocators | |||||
File Cache Block Size | 断片化を避けるため、ファイルキャッシュは独自のアロケーターを持っています。これは、そのブロックサイズです。 | memorysetup-cache-allocator-block-size |
4194304 |
||
Type Tree Block Size | 多数の小さな割り当てによる断片化を避けるため、Type Tree は独自のアロケーターを持っています。これは、そのブロックサイズです。 | memorysetup-typetree-allocator-block-size |
2097152 |
||
Shared Bucket Allocator | メインアロケーターが共有するバケットアロケーターです。 | ||||
Bucket Allocator Granularity | 共有アロケーター内のバケットのステップサイズです。 | memorysetup-bucket-allocator-granularity |
16 |
||
Bucket Allocator BucketCount | バケットサイズの数です。 | memorysetup-bucket-allocator-bucket-count |
8 |
||
Bucket Allocator Block Size | バケットに使用されるメモリブロックのサイズです。 | memorysetup-bucket-allocator-block-size |
Editor: 8388608 Player: 4194304
|
||
Bucket Allocator Block Count | 割り当てられるブロックの最大数です。 | memorysetup-bucket-allocator-block-count |
Editor: 8 Player: 1
|
||
Fast Per Thread Temporary Allocators | 非常に短寿命な割り当てを処理するスレッドローカルストレージ (TLS) アロケーターです。 | ||||
Main Thread Block Size | メインスレッドスタックの初期サイズです。 | memorysetup-temp-allocator-size-main |
Editor: 16777216 Player: 4194304
|
||
Job Worker Block Size | Unity のジョブシステムにおける各ジョブワーカーのサイズです。 | memorysetup-temp-allocator-size-job-worker |
E262144 |
||
Background Job Worker Block Size | 各バックグラウンドワーカーのサイズです。 | memorysetup-temp-allocator-size-background-worker |
32768 |
||
Preload Block Size | プリロードマネージャーのスタックサイズです。 | memorysetup-temp-allocator-size-preload-manager |
Editor: 33554432 Player: 262144
|
||
Audio Worker Block Size | 各オーディオワーカースレッドのスタックサイズです。 | memorysetup-temp-allocator-size-audio-worker |
65536 |
||
Cloud Worker Block Size | クラウドワーカースレッドのスタックサイズです。 | memorysetup-temp-allocator-size-cloud-worker |
32768 |
||
Gfx Thread Blocksize | メインレンダースレッドのスタックサイズです。 | memorysetup-temp-allocator-size-gfx |
262144 |
||
GI Baking Blocksize | 各 GI ワーカースレッドのスタックサイズです。 | memorysetup-temp-allocator-size-gi-baking-worker |
262144 |
||
NavMesh Worker Block Size | ナビメッシュワーカースレッドのスタックサイズです。 | memorysetup-temp-allocator-size-nav-mesh-worker |
65536 |
||
Fast Thread Shared Temporary Allocators | スレッド間で共有される短寿命の割り当てのための高速なリニアアロケーターです。 | ||||
Job Allocator Block Size | Unity が主にジョブワーカースレッドに使用する、ラウンドロビン方式のリニアスレッドアロケーターです。 | memorysetup-job-temp-allocator-block-size |
2097152 |
||
Background Job Allocator Block Size | バックグラウンドワーカー用のリニアアロケーターで、より長時間の割り当てが可能です。 | memorysetup-job-temp-allocator-block-size-background |
21048576 |
||
Job Allocator Block Size on low memory platforms | メモリが 2 GB 未満のプラットフォームでは、ジョブワーカーとバックグラウンドジョブの両方に、このサイズが使用されます。 | memorysetup-job-temp-allocator-reduction-small-platforms |
262144 |
||
Profiler Allocators | アプリケーションの割り当てパターンに干渉しないように、Unity がプロファイラー専用に使用するアロケーターです。 | ||||
Profiler Block Size | プロファイラーの主要部分のブロックサイズです。 | memorysetup-profiler-allocator-block-size |
16777216 |
||
Editor Profiler Block Size | プロファイラーのエディター部分のブロックサイズです。これはプレイヤーには存在しません。 | memorysetup-profiler-editor-allocator-block-size |
1048576 |
||
Shared Profiler Bucket Allocator | プロファイラーとエディタープロファイラーのアロケーター用の、共有バケットアロケーターです。 ローメモリのプラットフォームでは存在しません。 |
||||
Bucket Allocator Granularity | 共有アロケーター内のバケットのステップサイズです。 | memorysetup-profiler-bucket-allocator-granularity |
16 |
||
Bucket Allocator BucketCount | バケットサイズの数です。例えば、値が 4 の場合、サイズは 16、32、48、64 です。 | memorysetup-profiler-bucket-allocator-bucket-count |
8 |
||
Bucket Allocator Block Size | バケットに使用されるメモリブロックのサイズです。 | memorysetup-profiler-bucket-allocator-block-size |
Editor: 33554432 Player: 4194304
|
||
Bucket Allocator Block Count | 割り当てられるブロックの最大数です。 | memorysetup-profiler-bucket-allocator-block-count |
Editor: 8 Player: 1
|
ヒント: 行った設定がパフォーマンスを向上させることを確認するために、変更前と変更後にアプリケーションのプロファイリングを行ってください。詳しくは、プロファイラー概要のページ を参照してください。メモリ使用量レポートも提供されます。これはプレイヤーやエディターを終了した時にログ内で確認できます。ログファイルを見つけるには、ログファイルのページ の説明に従ってください。
Unity はアロケーターの設定を MemorySettings.asset
に格納し、これが、変更された設定をビルド時に boot.config
ファイルに入力します。つまり、新しい設定の効果はビルドごとに反映されます。
エディターでは、boot.config
は ProjectSettings
フォルダー内にあります。これは、Unity が MemorySettings.asset
のインポートや変更を行うたびに更新されます。エディター用の新しい値の効果は、次回のエディター起動時にのみ反映されます。