素晴らしい体験を実現しつつ、それをさまざまなデバイスやプラットフォームでスムーズに動作させるのは難しいことです。私たちが改良されたフレームタイミングマネージャーなど、全体的な最適化のためのツールに磨きをかけ続けているのは、これが理由です。この記事の続きでは、Unity 2022.1 でのアップデートにより、この機能のプラットフォームサポートが強化され、従来よりも多くのデータを収集できるようになったことをご紹介します。
フレームタイミングマネージャーで何ができるのか
フレームタイミングマネージャーは、フレーム全体の CPU 時間や GPU 時間の合計など、フレームレベルの時間計測を行う機能です。汎用の Unity プロファイラーや Profiler API と比較して、フレームタイミングマネージャーは非常に特殊なタスクのために設計されているため、パフォーマンスのオーバーヘッドが非常に低くなっています。最も重要なフレームの統計情報のみを強調するため、収集する情報量は慎重に制限されています。
フレームタイミングマネージャーを活用する主な理由の 1 つは、パフォーマンスのボトルネックをより詳細に調査することです。これによって、何がアプリケーションの性能を抑制しているのかを判断することができます。CPU のメインスレッドバウンドまたはレンダースレッドバウンドなのか、それとも GPU バウンドなのかといったことがわかります。分析結果に基づき、パフォーマンスを向上させるためのさらなるアクションを起こすことができます。
動的解像度機能は、検出されたボトルネックを GPU 側で修正することをサポートします。そして、レンダリング解像度を上げたり下げたりして、GPU の仕事量をダイナミックにコントロールすることができます。
開発中に、アプリケーション HUD でタイミングを視覚化することもでき、リアルタイムで高レベルなミニプロファイラーをアプリケーションに直接組み込むことができます。こうすることで、いつでもすぐに使えるようになるのです。
最後に、フレームタイミングマネージャーを使用して、リリースモードでのパフォーマンスレポートを作成することができます。収集した情報をもとに、さまざまなプラットフォームでのアプリケーションのパフォーマンスに関する統計情報をサーバーに送信し、全体的な意思決定を改善することができます。
Frame Timing Manager API は、どのような測定を可能にするのか
Frame Timing Manager API は、フレームごとの有用な CPU と GPU の測定値のセットを FrameTiming 構造体として提供します。その一覧をご紹介します。
cpuMainThreadPresentWaitTime は、表示されている [wait] ブロックの合計です。Present() とターゲット FPS に調整するための待ち時間を含んでいることに注意してください。「Scene rendering」の途中から始まって、次のフレームの前のフレームとの同期点で終わるので、GPU の作業時間を表示するのは難しいです。
始めよう
まず注目すべきは、フレームタイミングマネージャーが開発ビルドで常にアクティブであることです。開発時のみ使用する予定であれば、追加の手順は必要ありません。Frame Timing Manager C# API またはそのカウンターを使用するだけです。
リリースビルドの場合、その機能を使用する前に明示的にアクティベートする必要があります。その方法は複数あります。簡単な方法としては、プロジェクトのプレイヤー設定でチェックボックスをオンにすることです。この場合、C# API を使用してデータを読み取ることができます。しかし残念ながら、これは最も効率の悪い方法です。設定でこの機能を有効にすると、特定の時点で必要であるかどうかにかかわらず、この機能は有効なままになります。
using Unity.Profiling;
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
FrameTiming[] m_FrameTimings = new FrameTiming[10];
void Update()
{
// Instruct FrameTimingManager to collect and cache information
FrameTimingManager.CaptureFrameTimings();
// Read cached information about N last frames (10 in this example)
// The returned value tells how many samples is actually returned
var ret = FrameTimingManager.GetLatestTimings((uint)m_FrameTimings.Length, m_FrameTimings);
if (ret > 0)
{
// Your code logic here
}
}
}
また、Profiler Recorder API を使用して、フレームタイミングマネージャーの値を読み取ることもできます。Profiler Recorder API の利点は、フレームタイミングマネージャーの測定は、カウンターにレコーダーを取り付けたときのみ行われ、機能とそのオーバーヘッドをダイナミックに制御できることです。
using Unity.Profiling;
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
ProfilerRecorder mainThreadTimeRecorder;
void OnEnable()
{
// Create ProfilerRecorder and attach it to a counter
mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "CPU Main Thread Frame Time");
}
void OnDisable()
{
// Recorders must be explicitly disposed after use
mainThreadTimeRecorder.Dispose();
}
void Update()
{
var frameTime = mainThreadTimeRecorder.LastValue;
// Your code logic here
}
}
フレームタイミングマネージャーから提供されるデータは、ボトルネックの検出に使用することができます。最も簡単な方法は、メインスレッド CPU、レンダースレッド CPU、Present Wait、GPU の時間を比較し、フレームレートを下げる最大かつ最も関与している可能性の高い原因がどれかを判断することで す。例えば以下のようにします。
using Unity.Profiling;
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
internal enum PerformanceBottleneck
{
Indeterminate, // Cannot be determined
PresentLimited, // Limited by presentation (vsync or framerate cap)
CPU, // Limited by CPU (main and/or render thread)
GPU, // Limited by GPU
Balanced, // Limited by both CPU and GPU, i.e. well balanced
}
FrameTiming[] m_FrameTimings = new FrameTiming[1];
void Update()
{
FrameTimingManager.CaptureFrameTimings();
var ret = FrameTimingManager.GetLatestTimings((uint)m_FrameTimings.Length, m_FrameTimings);
if (ret > 0)
{
var bottleneck = DetermineBottleneck(m_FrameTimings[0]);
// Your code logic here
}
}
static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s)
{
const float kNearFullFrameTimeThresholdPercent = 0.2f;
const float kNonZeroPresentWaitTimeMs = 0.5f;
// If we're on platform which doesn't support GPU time
if (s.GPUFrameTime == 0)
return PerformanceBottleneck.Indeterminate;
float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime;
// GPU time is close to frame time, CPU times are not
if (s.GPUFrameTime > fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.GPU;
// One of the CPU times is close to frame time, GPU is not
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
(s.MainThreadCPUFrameTime > fullFrameTimeWithMargin ||
s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin))
return PerformanceBottleneck.CPU;
// Main thread waited due to Vsync or target frame rate
if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs)
{
// None of the times are close to frame time
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.PresentLimited;
}
return PerformanceBottleneck.Balanced;
}
}
フレームタイミングマネージャーは、シンプルなオンスクリーンプロファイラーとして使用することができ、アプリケーションの健全性を評価するために便利に使うことができます。その最も基本的な形は、以下のようになるでしょう。
using System;
using UnityEngine;
using Unity.Profiling;
public class FrameTimingsHUDDisplay : MonoBehaviour
{
GUIStyle m_Style;
readonly FrameTiming[] m_FrameTimings = new FrameTiming[1];
void Awake()
{
m_Style = new GUIStyle();
m_Style.fontSize = 15;
m_Style.normal.textColor = Color.white;
}
void OnGUI()
{
CaptureTimings();
var reportMsg =
$"\nCPU: {m_FrameTimings[0].cpuFrameTime :00.00}" +
$"\nMain Thread: {m_FrameTimings[0].cpuMainThreadFrameTime:00.00}" +
$"\nRender Thread: {m_FrameTimings[0].cpuRenderThreadFrameTime:00.00}" +
$"\nGPU: {m_FrameTimings[0].gpuFrameTime:00.00}";
var oldColor = GUI.color;
GUI.color = new Color(1, 1, 1, 1);
float w = 300, h = 210;
GUILayout.BeginArea(new Rect(32, 50, w, h), "Frame Stats", GUI.skin.window);
GUILayout.Label(reportMsg, m_Style);
GUILayout.EndArea();
GUI.color = oldColor;
}
private void CaptureTimings()
{
FrameTimingManager.CaptureFrameTimings();
FrameTimingManager.GetLatestTimings(m_FrameTimings.Length, m_FrameTimings);
}
}
対応プラットフォームと制限事項
フレームタイミングマネージャーは、以下の例外を除き、Unity がサポートするすべてのプラットフォームをサポートしています。
フレームタイミングマネージャーの重要な実装仕様は以下の通りです。
高度なトピック
知識のあるユーザーであれば、フレームタイミングマネージャーを使ってフレームタイムラインの可視化や他のマーカーとの差分計算に使用できるタイムスタンプ情報の取得を行うことができます。
提供されるタイムスタンプは以下の通りです。
ご意見をお聞かせください
これらの改善が、皆さんのアプリケーションごとに固有のパフォーマンスストーリーを測定し、理解するのに役立つことを期待しています。こうした優れた機能を、すでに Unity 2022.1 でご利用することができます。
プロファイリングツールの今後の展開が気になる方は、こちらのロードマップ をご覧ください。何かありましたら、私たちのフォーラムページを通して、お気軽にチームにご連絡ください。皆さんのご意見を伺いながら、Unity のパフォーマンス関連の機能やツールをさらに向上させる方法を考えていきたいと思います。