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

Lilli Thompson
Lilli Thompson

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

Kelvin ロード

HTML5 ゲームの実行速度を上げるには、まずパフォーマンスのボトルネックを特定する必要がありますが、その作業は簡単ではありません。フレーム/秒(FPS)データの評価は出発点ですが、全体像を確認するには、Chrome のアクティビティ内の微妙な違いを把握する必要があります。

about:tracing ツールは、パフォーマンスの改善を目的として急いでいる回避策を回避するための分析情報を提供します。これは本質的には意図的な推測によるものです。多くの時間とエネルギーを節約でき、Chrome が各フレームで何を行っているかをより明確に把握でき、この情報を使用してゲームを最適化できます。

トレースについて

Chrome の about:tracing ツールを使用すると、Chrome の一定期間のすべてのアクティビティを確認できます。最初はあまりに粒度が良くないので、大変に感じるかもしれません。Chrome の多くの機能はすぐにトレースできるようにインストゥルメント化されているため、手動でインストルメンテーションを行うことなく、about:tracing を使用してパフォーマンスを追跡できます。(JS を手動でインストルメント化する方法については、後述のセクションをご覧ください)。

トレースビューを表示するには、「about:tracing」と入力します。アドレスバーに入力します。

<ph type="x-smartling-placeholder">
</ph> Chrome のアドレスバー
「about:tracing」と入力します。アドレスバーに

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

<ph type="x-smartling-placeholder">
</ph> シンプルなトレース結果
シンプルなトレース結果

うん、わかりにくいですね。では、その読み方を見ていきましょう。

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

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

<ph type="x-smartling-placeholder">
</ph> ハイライト表示されたシンプルなトレース結果
ハイライト表示されたシンプルなトレース結果

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

フレームを探しています

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

<ph type="x-smartling-placeholder">
</ph> 3 つの実行フレームがあるように見える
3 つの実行フレームがあるように見える

ゲームがどこで動作しているかがわかれば、各フレームでコードがどのような処理を行っているかを詳しく調べることができます。ファンクション ボックスのテキストが読めるまで W、A、S、D を使用してズームインします。

<ph type="x-smartling-placeholder">
</ph> 実行フレームの詳細
実行フレームの詳細

この一連のボックスは一連の関数呼び出しを示し、各呼び出しは色付きのボックスで示されます。各関数は上のボックスによって呼び出されたので、この場合は 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");

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

<ph type="x-smartling-placeholder">
</ph> 手動で追加したタグ
手動で追加されたタグ

これを使用して、人が読める形式のトレースデータを作成し、コード内のホットスポットを追跡できます。

GPU か CPU か

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

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

GPU と CPU のトレース

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

トレースビューは、ゲームの実行速度が遅く、どのリソースが最大限まで使用しているか不明な場合に役立ちます。GPU と CPU の線の関連性を確認することが、デバッグの鍵となります。先ほどと同じ例を使って、更新ループに少し処理を追加します。

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

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

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

GPU と CPU のトレース

このトレースから何がわかりますか?表示されているフレームは約 2,270 ms から 2,320 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);
  }
}

このシェーダーを使用したコードのトレースは、どのようなものになりますか。

<ph type="x-smartling-placeholder">
</ph> 低速な GPU コードを使用する場合の GPU と CPU のトレース
遅い GPU コードを使用する場合の GPU と CPU のトレース

ここでも、フレームの長さに注意してください。ここでは、繰り返しパターンは約 2750 ms から 2, 950 ms で、持続時間は 200 ms(約 5 Hz のフレームレート)です。CrRendererMain の行はほぼ完全に空になっています。つまり、ほとんどの時間で CPU がアイドル状態になり、GPU が過負荷状態になっています。これは、シェーダーが重すぎることを示しています。

低フレームレートの原因が正確にわからない場合、5 Hz の更新が観察され、ゲームコードを調べてゲームロジックの最適化または削除を試みたくなるかもしれません。この場合、ゲームループ内のロジックが時間を浪費しているわけではないため、まったく意味がありません。実際、このトレースが示すのは、各フレームでより多くの CPU 処理を行うと本質的に「解放」状態になるということです。CPU がアイドル状態で待機しているため、処理を増やしてもフレームの所要時間に影響しません。

実例

では、実際のゲームのトレースデータがどのようなものか確認してみましょう。オープンウェブ テクノロジーで構築されたゲームの素晴らしい点の一つは、お気に入りのプロダクトで何が起こっているかを確認できることです。プロファイリング ツールを試したい場合は、Chrome ウェブストアからお気に入りの WebGL タイトルを選択し、about:tracing でプロファイリングできます。これは、優れた WebGL ゲーム Skid Racer から取得したトレースの例です。

<ph type="x-smartling-placeholder">
</ph> 本物のゲームをトレースする
本物のゲームを追う

各フレームにかかる時間は約 20 ミリ秒で、フレームレートは約 50 FPS です。作業は CPU と GPU でバランスが取れていますが、最も需要が高いのは GPU のリソースです。WebGL ゲームの実例をプロファイリングして確認したい場合は、WebGL で構築された Chrome ウェブストアのタイトルの一部を試してみてください。

まとめ

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

次のステップ

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

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