本記事は、サムザップ Advent Calendar 2021の12/25の記事です。
はじめに
アプリを安定してストレスなく動作させるため重要なパフォーマンスチューニングですが、
プロファイリング・修正・ビルドを繰り返し繰り返し行う根気の必要な作業です。
今回はそのパフォーマンスチューニングのうち、プロファイリング工程を円滑に進めるためのTipsを3つ紹介したいと思います。
1. プログラムから計測のタイミングをコントロールする
UnityのProfiler APIを活用することで、プログラムでプロファイリングの開始・終了をコントロールできます。
以下のコードでは、シーンの初期化処理の始めから終わりまでを計測しています。
public class Scene
{
...
public async Task InitializeAsync()
{
// 計測を開始する
Profiler.logFile = LOG_FILE_PATH;
Profiler.enableBinaryLog = true;
Profiler.enableLog = true;
await _componentA.InitializeAsync();
await _componentB.InitializeAsync();
// 計測を終了する
Profiler.logFile = null;
Profiler.enableLog = false;
}
}
プロファイラウィンドウから手で計測をコントロールする場合はタイミングを合わせるのが難しいため、どうしても余分に計測しがちですが、
プログラムからであれば関心のある処理を過不足なく計測することができます。
Profile Analyzerでパフォーマンスチューニング前後のデータの比較を行う場合も、
過不足なくデータが取れていれば、比較対象のフレームを改めて指定する必要がないのでスムーズに分析ができると思います。
計測したデータは Profiler.logFile
で指定したパスへ書き出されますが、
これはAndroidであれば、adb pullコマンドやUSB ファイル転送モードで端末を接続することで取得できます。
2. Deep Profilingと通常のProfilingを併用する
Deep Profilingはメソッド1つ1つの詳細な計測ができるため、
非同期メソッド等、メソッド呼び出しのネストが深い箇所のボトルネックの洗い出しには必須とも言えます。
しかしながら常にDeep Profilingオンで良いかと言うと、そうとも言えない場合があります。
たとえば以下のコードのような、ファイルの中身を読み込む処理と、データを復号する処理があるとします。
public byte[] LoadBytes(string path)
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
var bytes = new byte[stream.Length];
var count = bytes.Length;
var offset = 0;
while (count > 0)
{
var n = stream.Read(bytes, offset, count);
if (n == 0) break;
offset += n;
count -= n;
}
return bytes;
}
public string Decrypt(byte[] data, byte[] key, byte[] iv)
{
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
using var source = new MemoryStream(data, false);
using var decrypt = new CryptoStream(source, decryptor, CryptoStreamMode.Read);
using var reader = new StreamReader(decrypt, Encoding.UTF8);
return reader.ReadToEnd();
}
この処理をDeep Profilingオフとオンでそれぞれプロファイリングすると、
Deep Profilingオフ
Deep Profilingオン
となります。
Deep Profilingのオンとオフで比較した場合、Decryptメソッドの処理時間が + 1494.19ms
と大幅に増えているのに対し、
LoadBytesメソッドの処理時間は + 2.79ms
でしかなく、大きな差が出ています。
この差を詳しく見るため、Deep Profilingオンのときのそれぞれの処理を展開してみると、
Decrypt
LoadByes
処理内でメソッドを呼び出している数(Calls
)に大きな差があることが読み取れます。
恐らくDeep Profilingの計測のオーバーヘッドが積み重なっていると思われますが、
このようにメソッド呼び出しの多い処理はそうでない処理に比べて、処理時間が誇張されてしまうことに注意が必要です。
したがって、Deep Profilingオンで見つけたボトルネックを直したけれどもリリースビルドではそれほど改善されていなかった、という事態を避けるためにも、
Deep ProfilingだけではなくCustomSamplerやProfileMarkerを使用した通常のProfilingも併用すると良いでしょう。
3. Standaloneプロファイラを活用する
Unity 2020より、プロファイラを独立したプロセスで動作させる機能が追加されました。
Window > Analysis > Profiler (Standalone Process)
またPackage ManagerでProfile Analyzerをインストールしていれば、
プロファイラウィンドウのWindow > Analysis > Profile Analyzerより、独立したプロセス上でProfile Analyzerを扱えます。
これにより、プロファイリングデータのロード・分析中にアセットの修正やスクリプトの編集を行ったり、
また逆にアプリのビルドやスクリプトのコンパイルの影響を受けずに分析を進められます。
ともすればGB単位にもなるプロファイリングデータをロード・分析するのは時間がかかりますので、是非この機能を活用されてはいかがでしょうか。
Unity 2019以前ではこの機能はありませんが、
別のUnityプロジェクトを作成し、そこでプロファイラウィンドウを開くことで代替できます。
(ただしプロファイリング対象とプロファイラの両プロジェクトでUnityバージョンを合わせる必要が有ります)
まとめ
- プログラムで計測の開始と終了をコントロールし、安定した正確な計測をしよう
- Deep Profilingの計測のオーバーヘッドを知り、通常のProfilingも併用してボトルネックを正確に特定しよう
- Standaloneプロファイラを使って分析と改善を円滑に進めよう
以上、この記事が何か1つでもプロファイリングの助けになれれば幸いです。