渋谷ほととぎす通信

新しいこと、枯れたこと問わずサムザップ大庭が興味を持ったものを調査、生活の効率を求める完全趣味の技術ブログ。基礎を大事にしています。※あくまで個人ブログであり所属組織とは関係ありません

シェーダバリアントが多く作られるシェーダの最大使用メモリを計測する


Unityでキーワードを追加して処理を分岐させながらシェーダを書いていくと、知らず知らずのうちにシェーダバリアントが大量に増えることがあります。シェーダバリアントを作成するためには#prgama multi_compileまたは#pragma shader_featureを追加だけなのでとても簡単です。


以前multi_compileについて執筆したので参考にどうぞ。

シェーダバリアントが増えることによってコンパイルしたシェーダが占有するメモリが増えていきます。本記事ではシェーダバリアント増加によるメモリ使用量の計測について説明していきいます。

multi_compileとshader_featureの違い

前述した通りmulti_compileとshader_featureはシェーダバリアントを作成するために使用します。

  • shader_featureはビルド時に使用している組み合わせのみコンパイルされる
  • multi_compileは定義した組み合わせ全てをコンパイルされる

という大きな違いがあります。
基本はshader_featureでシェーダバリアントを作成した方が生成される個数は最小限に抑える事ができそうです。


Uniteでのポストモーテムセッションや技術ブログを読むと、最適化する上でmulti_compileからshader_featureに置き換える話を見かけます。


そして、ここからが本題です。

シェーダバリアントが最大でどのくらいのメモリを使用するかを把握する必要性がある

shader_featureで指定されているシェーダバリアントが、本来どのくらい生まれるかは開発、運用してみないと分かりません。


なぜなら前述した通り、ビルド時に使用しているキーワードの組み合わせしか作成されないためです。運用を開始してからシェーダを改修するのは難易度が非常に高くなるため、開発中(せめて中盤頃)にコンパイルしたシェーダが占めるの最大使用メモリは決めておきたいところです。


もう少し詳しく説明すると、集団開発する上でシェーダはエンジニア以外も触ります。Unityエンジニアが想定する範囲内での使用なら問題ないですが、例えばクリエータがそういった暗黙の決まりを知らずにシェーダを使ってしまう場合もあります。
すると、いつの間にかシェーダバリアントが爆増して、メモリ使用量が増えてアプリが落ちたり、またシェーダのコンパイルに依るスパイク(カクツキ)が問題になったりします。

※スパイクについては本記事では割愛


以上の理由からUnityエンジニアはシェーダバリアントの影響範囲を把握しておくべきだと考えます。

一時的にshader_featureをmulti_compileに置き換えてみる

どうやってシェーダバリアントの影響範囲を把握するかですが、一時的にshader_featureをmulti_compileに置き換えて検証してみます。

こうすることで全てのシェーダバリアントを作成することができます。この状態でコンパイルしたシェーダが占めるメモリを計測して、そのシェーダのポテンシャルを測ります。

計測方法

UnityEditor上で実行するプロファイラの値にはノイズが入りすぎててあてにならないので、必ず実機を使って計測するようにしています。具体的にはUnityEditorのProfilerに実機(iPhone6s)を繋ぎ、その中のMemoryProfilerで確認します。

※PackageからインストールするMemoryProfilerではありません

f:id:esakun:20200504163023p:plain:w350 Window > Analysis > Profilerから起動します。

MemoryProfilerのグラフ

シェーダコンパイル前

f:id:esakun:20200504152658p:plain:w300

シェーダコンパイル後

f:id:esakun:20200504152711p:plain:w300 Total Allocatedの値が大きく変化しています。
このビューでは詳しいことは分からないので詳細を見ていきます。

MemoryProfilerのSimpleビュー

シェーダコンパイル前

f:id:esakun:20200504152909p:plain:w450

シェーダコンパイル後

f:id:esakun:20200504152932p:plain:w450

UnityProfiler => MemoryProfilerのSimpleビューで確認すると、Unityが管理するネイティブメモリが40MBほど増えている事が分かります。

Detailビューで更に詳しく見ていきます。

MemoryProfilerのDetailビュー

シェーダコンパイル前

f:id:esakun:20200504153448p:plain:w450

シェーダコンパイル後

f:id:esakun:20200504153301p:plain:w450

Other > Rendering > ShaderLabの値が約40MBほど増加しています。コンパイルしたシェーダはネイティブメモリとして計上されているようです。

f:id:esakun:20200504163802p:plain:w350

Otherが何者なのかについては以下のリファレンスを参考にどうぞ。
Memory プロファイラー - Unity マニュアル

サードパーティMemoryProfilerでは確認できない

ここは余談になります。

UnityのMemoryProfilerに2種類あります。
Profiler付属製とPackageManager経由でインストールするサードパーティ製です。

f:id:esakun:20200504160010p:plain:w450 サードパーティ製のMemoryProfilerはこのようにグラフィカルにメモリ分布を表示してくれてとても見やすいのですが、コンパイルしたシェーダのメモリ使用量は表示してくれないようなので注意です。

念のためにXcodeで確認

UnityEditorでのプロファイリングでほぼほぼ事足りますが、計測した値が正しいかXcodeでのプロファイリングもしてみます。

シェーダコンパイル前

f:id:esakun:20200504164650p:plain:w450

シェーダコンパイル後

f:id:esakun:20200504164632p:plain:w450

約57MB増えています。UnityEditorで計測する値と一致はしませんが、これが実際にこのアプリで使用されているメモリということになります。そこまで大きく値がずれていないという事が確認できました。

Xcode付属のプロファイラInstrumentsを使用すれば更に詳しく確認できると思いますが、今回は時間の関係上調査しません。

f:id:esakun:20200504165313p:plain:w450 UnityEditor上では気づかなかったのですが、シェーダコンパイル時に一瞬ピークメモリが跳ね上がるのも気になります。 こちらも同様Instrumentsで確認すれば何かしら要因が見えてくるかもしれません。

最後に

multi_compile、shader_featureとシェーダバリアントという機能はとても便利です。フルスクラッチでシェーダをこれらのパターン書く事を考えると本当に神機能だと思います。

ただ神機能も調子にのって使いすぎるとシェーダバリアントが大量に作られてしまい、メモリを大量に使用してしまっていたという事も発生します。


最大でどのくらいメモリを消費するかを把握し、アプリ全体のピークメモリを制御する事で安全に開発を進められたらと思います。また日々実機でMemoryProfilerの値をチェックして異常な値になっていないかをウォッチする事も大事。

環境

  • macOS HighSierra 10.13.6
  • Xcode10.1
  • Unity2019.2.8f1
  • 検証実機 iPhone6s ios10.3.1

参考