about:tracing フラグを使用した WebGL ゲームのプロファイリング

Lilli Thompson
Lilli Thompson

測定できなければ、改善することはできません。

Lord Kelvin

HTML5 ゲームの実行速度を上げるには、まずパフォーマンスのボトルネックを特定する必要がありますが、これは難しい場合があります。フレームレート(FPS)データを評価することは最初のステップですが、全体像を把握するには、Chrome アクティビティの微妙な違いを把握する必要があります。

about:tracing ツールは、パフォーマンスの改善を目的とした、基本的には善意の推測に過ぎない、性急な回避策を回避するのに役立つ分析情報を提供します。時間と労力を大幅に節約し、Chrome が各フレームで何を行っているかを明確に把握して、その情報をゲームの最適化に活用できます。

こんにちは about:tracing

Chrome の about:tracing ツールでは、一定期間の Chrome のすべてのアクティビティを非常に詳細に確認できます。最初は圧倒されるかもしれませんが、Chrome の多くの関数は、トレースを目的として標準で計測されています。そのため、手動で計測しなくても、about:tracing を使用してパフォーマンスをトラッキングできます。(JS を手動で計測する方法については、後のセクションをご覧ください)。

トレースビューを表示するには、Chrome のオムニボックス(アドレスバー)に「about:tracing」と入力します。

Chrome のアドレスバー
Chrome のアドレスバーに「about:tracing」と入力

トレースツールから録画を開始し、ゲームを数秒間実行して、トレースデータを表示できます。データの例を次に示します。

シンプルなトレース結果
シンプルなトレース結果

はい、わかりにくいですね。読み方について説明します。

各行はプロファイリング対象のプロセスを表し、左右の軸は時間を示します。色付きのボックスは、インストルメンテーションされた関数呼び出しです。さまざまな種類のリソースの行があります。ゲームのプロファイリングで最も興味深いのは、グラフィック プロセッシング ユニット(GPU)の処理内容を示す CrGpuMain と CrRendererMain です。各トレースには、トレース期間中に開いているタブ(about:tracing タブ自体を含む)ごとに CrRendererMain 行が含まれます。

トレースデータを読み取る際の最初のタスクは、ゲームに対応する CrRendererMain の行を特定することです。

シンプルなトレース結果がハイライト表示
シンプルなトレース結果がハイライト表示されています

この例では、2 つの候補は 2216 と 6516 です。残念ながら、定期的に多くの更新を行っている行を探す(または、トレースポイントでコードを手動で計測している場合は、トレースデータを含む行を探す)以外に、アプリケーションを特定する洗練された方法は現在ありません。この例では、更新頻度から、6516 がメインループを実行しているようです。トレース開始前に他のタブをすべて閉じておくと、正しい CrRendererMain を見つけやすくなります。ただし、ゲーム以外のプロセスの CrRendererMain 行が存在することもあります。

フレームを見つける

ゲームのトレーシング ツールで正しい行を見つけたら、次はメインループを見つけます。メインループは、トレースデータ内の繰り返しパターンのように見えます。トレースデータ内を移動するには、W、A、S、D キーを使用します。A キーと D キーは左右(時間軸の前後)に移動し、W キーと S キーはデータのズームインとズームアウトに使用します。ゲームが 60 Hz で実行されている場合、メインループは 16 ミリ秒ごとに繰り返されるパターンになります。

3 つの実行フレームのように見える
3 つの実行フレームのように見える

ゲームのハートビートを特定したら、各フレームでコードが何を行っているかを詳しく調べることができます。W、A、S、D キーを使用して、関数ボックスのテキストが読み取れるまで拡大します。

実行フレームの詳細
実行フレームの詳細

このボックスの集合は、一連の関数呼び出しを示しています。各呼び出しは色付きのボックスで表されます。各関数は、その上のボックスによって呼び出されています。この例では、MessageLoop::RunTask が RenderWidget::OnSwapBuffersComplete を呼び出し、それが RenderWidget::DoDeferredUpdate を呼び出しています。このデータを読み取ることで、何が何を呼び出したか、各実行にかかった時間などをすべて把握できます。

ただし、ここで少し問題が発生します。about:tracing によって公開される情報は、Chrome ソースコードからの未加工の関数呼び出しです。各関数の処理内容は名前から推測できますが、必ずしもユーザーにとってわかりやすい情報とは言えません。フレームの全体的な流れを確認するには便利ですが、実際に何が起こっているかを把握するには、人間が読み取りやすいものが必要です。

トレースタグの追加

幸い、console.timeconsole.timeEnd という、コードに手動計測を追加してトレースデータを作成する簡単な方法があります。

console.time("update");
update
();
console
.timeEnd("update");
console
.time("render");
update
();
console
.timeEnd("render");

上記のコードでは、指定されたタグを使用してトレースビュー名に新しいボックスが作成されます。そのため、アプリを再実行すると、「更新」ボックスと「レンダリング」ボックスが表示され、各タグの開始呼び出しと終了呼び出しの間の経過時間が示されます。

手動で追加されたタグ
手動で追加されたタグ

これを使用して、人が読み取れるトレースデータを作成し、コードのホットスポットをトラッキングできます。

GPU と CPU

ハードウェア アクセラレーションのグラフィックでは、プロファイリング中に確認すべき最も重要な質問の 1 つは、このコードは GPU バウンドか CPU バウンドかということです。フレームごとに、GPU でレンダリング処理を行い、CPU でロジック処理を行います。ゲームの遅延の原因を把握するには、2 つのリソース間で処理がどのように分散されているかを確認する必要があります。

まず、特定の時点で GPU がビジーかどうかを示す CrGPUMain という名前の行をトレースビューで探します。

GPU と CPU のトレースを取得する

ゲームのフレームごとに、CrRendererMain と GPU で CPU 処理が発生していることがわかります。上記のトレースでは、16 ms のフレームのほとんどで CPU と GPU の両方がアイドル状態になる非常にシンプルなユースケースを示しています。

トレースビューは、ゲームの実行速度が遅く、どのリソースが最大限に使用されているかわからない場合に特に役立ちます。GPU と CPU の線がどのように関連しているかを確認することが、デバッグの鍵となります。前と同じ例を取り上げますが、更新ループに少し余分な処理を追加します。

console.time("update");
doExtraWork
();
update
(Math.min(50, now - time));
console
.timeEnd("update");

console
.time("render");
render
();
console
.timeEnd("render");

次のようなトレースが表示されます。

GPU と CPU のトレースを取得する

このトレースからわかることは次のとおりです。画像のフレームは約 2270 ms から 2320 ms に変化しています。つまり、各フレームには約 50 ms(フレームレート 20 Hz)かかります。更新ボックスの横に、レンダリング関数を表す色付きのボックスの一部が表示されますが、フレームは更新自体で占められています。

CPU の状況とは対照的に、GPU は各フレームのほとんどでアイドル状態のままであることがわかります。このコードを最適化するには、シェーダー コードで実行できるオペレーションを探し、リソースを最大限に活用するために GPU に移動します。

シェーダー コード自体が遅く、GPU が過負荷になっている場合はどうなりますか?CPU から不要な処理を削除し、代わりにフラグメント シェーダー コードに処理を追加するとどうなりますか。次に、不必要にコストの高いフラグメント シェーダーを示します。

#ifdef GL_ES
precision highp
float;
#endif
void main(void) {
 
for(int i=0; i<9999; i++) {
    gl_FragColor
= vec4(1.0, 0, 0, 1.0);
 
}
}

そのシェーダーを使用するコードのトレースはどうなっているでしょうか。

遅い GPU コードを使用している場合の GPU と CPU のトレース
遅い GPU コードを使用している場合の GPU と CPU のトレース

フレームの時間を確認します。ここでは、繰り返しパターンが約 2,750 ms から 2,950 ms まで、200 ms 間続いています(フレームレートは約 5 Hz)。CrRendererMain 行はほぼ空白です。つまり、CPU はほとんどアイドル状態であり、GPU は過負荷状態です。これはシェーダーが重すぎることを示しています。

フレームレートが低下する原因を正確に把握していない場合、5 Hz の更新を観察して、ゲームコードにアクセスしてゲームロジックを最適化または削除しようとする可能性があります。この場合、ゲームループのロジックが時間の消費の原因ではないため、これはまったく効果がありません。実際、このトレースは、CPU がアイドル状態になっているため、各フレームで CPU 処理を増やしても基本的に「無料」であり、処理を増やしてもフレーム時間に影響しないことを示しています。

実際の例

では、実際のゲームのトレーシング データの例を見てみましょう。オープンウェブ技術で構築されたゲームの魅力の一つは、お気に入りのサービスで何が起こっているかを確認できることです。プロファイリング ツールをテストするには、Chrome ウェブストアからお気に入りの WebGL タイトルを選択し、about:tracing でプロファイリングします。これは、優れた WebGL ゲームである Skid Racer から取得したトレースのサンプルです。

実際のゲームをトレースする
実際のゲームのトレース

各フレームに 20 ミリ秒ほどかかるようです。つまり、フレームレートは約 50 FPS です。処理は CPU と GPU の間でバランスが取れていますが、GPU が最も需要の高いリソースです。WebGL ゲームの実際の例をプロファイリングする方法を確認するには、WebGL で作成された Chrome ウェブストアのタイトルを試してみてください。以下に例を示します。

まとめ

ゲームを 60 Hz で実行する場合、すべてのオペレーションをフレームごとに 16 ミリ秒の CPU 時間と 16 ミリ秒の GPU 時間に収める必要があります。2 つのリソースを並行して使用でき、その間で作業をシフトしてパフォーマンスを最大化できます。Chrome の about:tracing ビューは、コードが実際に何を行っているかを把握するのに非常に役立つツールです。また、適切な問題に取り組むことで、開発時間を最大限に活用できます。

次のステップ

GPU 以外にも、Chrome ランタイムの他の部分をトレースすることもできます。Chrome Canary は Chrome の初期段階のバージョンであり、IO、IndexedDB、その他のアクティビティをトレースするように計測されています。トレース イベントの現在の状態について詳しくは、こちらの Chromium の記事をご覧ください。

ウェブゲームのデベロッパーの方は、以下の動画をご覧ください。これは、Google のゲーム デベロッパー アドボケイト チームが GDC 2012 で行った、Chrome ゲームのパフォーマンス最適化に関するプレゼンテーションです。