プロファイラー
Godotからゲームを実行してプレイしてみましょう。楽しくて、機能が完成しつつあり、リリースが近づいていると感じます。
しかしその後、スキルツリーを開くとコードに何かが引っかかり、スキルツリーが停止してしまいます。スキルツリーがスライドショーのようにスクロールするのは受け入れられません。何が悪かったのでしょう? スキルツリー要素の位置? UI? それともレンダリング?
すべてを最適化してゲームを繰り返し実行することもできますが、重い箇所の可能性を絞り込んでいくほうが賢いです。 そのためにGodotのプロファイラーを使用します。
プロファイラーの概要
プロファイラーを開くには、デバッガー パネルを開いて、プロファイラー タブをクリックします。
プロファイリングはパフォーマンスを重視するため、Godotのプロファイラは自動的には実行されません。ゲーム内で起こっているすべての事象を継続的に測定し、デバッガーに報告する必要があるため、デフォルトではオフになっています。
To begin profiling, run your game then focus back on the editor. Click on the Start button in the top-left corner of the Profiler tab. You can also check Autostart, which will make the profiler automatically start when the project is run the next time. Note that the Autostart checkbox's state is not preserved across editor sessions.
注釈
プロファイラーは現在、C#スクリプトをサポートしていません。 C#スクリプトはGodotサポートプラグインを備えた JetBrains Rider および JetBrains dotTrace を使用してプロファイリングできます。
クリア ボタンをクリックすると、いつでもデータをクリアできます。 測定 ドロップダウンメニューを使用して、測定するデータの種類を変更します。それに応じて測定パネルとグラフが更新されます。
測定データ
プロファイラーのインターフェースは2つに分かれています。左側に機能のリスト、右側にパフォーマンスのグラフがあります。
主な測定値は、フレーム時間、物理フレーム、待機時間、物理時間です。
Frame Time は物理からレンダリングまで、画像全体のすべてのロジックをGodotが実行するのにかかる時間です。
Physics Frame Time はGodotが物理更新の間に割り当てた時間です。理想的なシナリオではフレーム時間は選択したものになります。デフォルトでは 16.66 ミリ秒で、これは 60FPS に相当します。これは周囲のあらゆるものに使用できる参照フレームです。
Process time はGodot が物理以外のロジック (_process 内に存在するコードや、**アイドル時**に更新するように設定されたタイマーやカメラなど) を更新するのにかかった時間です。
Physics Time はGodot が _physics_process や組み込みノードなどの**物理**タスクを更新するのにかかった時間です。
注釈
**Frame Time**にはレンダリング時間が含まれます。ゲーム内で謎のラグの急増を見つけたものの、物理演算とスクリプトはすべて高速に実行されているとするとき、遅延はパーティクルの出現または視覚効果が原因である可能性があります!
デフォルトではGodotはフレーム時間と物理時間をチェックします。これにより設定された物理FPSと比較して各フレームにかかる時間の概要が得られます。左側のチェックボックスをクリックすると、機能のオンとオフを切り替えることができます。リストの下に進むとコードが表示されるスクリプト関数に到達する前に、Physics 2D、Physics 3D、Audioなどの他の機能も表示されます。
グラフをクリックすると、左側に表示されるフレームの情報が変わります。右上には表示しているフレームをより細かく手動で調整できるフレームカウンターもあります。
測定範囲と測定ウィンドウ
測定 のドロップダウンメニューを開くと、表示する測定値の種類を変更できます。デフォルトではフレームを通過するのにかかる時間をミリ秒単位でリストします。平均時間は特定の関数が複数回呼び出されたときにかかった平均時間です。たとえば、5回の実行に 0.05 ミリ秒かかった関数の平均時間は 0.01 ミリ秒になります。
正確なミリ秒数が重要ではなく、フレームの残りの部分に対する時間の割合を確認したい場合は、パーセンテージ測定を使用します。 フレーム%はフレーム時間に相対し、物理フレーム%は物理時間に相対します。
最後のオプションは時間の範囲です。 **包括**はネストされた関数呼び出しを含む関数にかかった時間を測定します。例えば:
get_neighbors 、 find_nearest_neighbor 、 move_subject はすべて時間がかかりました。これは3つすべてが遅いためだと思われる可能性があります。
ただし 自己 に変更すると、Godotは関数呼び出しの関係を考慮せずに、その関数本体で費やした時間を測定します。
get_neighbors と move_subject のパーセントが大幅に減っていることがわかります。実際にはこれは get_neighbors と move_subject が他の関数呼び出しが終了するまでの待機時間よりも待機時間の方が長く、 find_nearest_neighbor が**実際** のところ一番遅いことを意味します。
プロファイラーを使用した遅いコードのデバッグ
プロファイラーを使用して遅いコードを見つけるには、結局のところゲームを実行し、描画されるパフォーマンス グラフを観察することになります。フレーム時間内に許容できないスパイクが発生した場合は、グラフをクリックしてゲームを一時停止し、 "フレーム #" をスパイクの開始位置まで絞り込むことができます。根本原因を見つけるにはフレームと関数の間を行ったり来たりする必要がある場合があります。
時間がかかる関数を見つけるためには、 Script Functionsの下のいくつかの関数のチェックボックスをオンにします。これを使用してレビュー、最適化する必要がある関数を見つけます。
手動で測定する (マイクロ秒単位)
関数が複雑な場合、どの部分を最適化する必要があるかを把握するのが困難になる可能性があります。ボトルネックになっているところは計算処理自体なのか、計算を行うために他のデータにアクセスしているところなのか、 for ループなのか、 if 条件式なのか?
コードがいくつかの一時的な関数で実行されるとき、手動でティックをカウントすることで負荷の高い場所を絞り込むことができます。2つの関数は Time クラス オブジェクトの一部です。それは get_ticks_msec と get_ticks_usec です。前者はミリ秒 (1秒あたり 1,000) 単位で測定され、後者はマイクロ秒 (1 秒あたり 1,000,000) 単位で測定されます。
どちらもそれぞれの時間枠でゲーム エンジンが開始されてからの時間を返します。
マイクロ秒の開始カウントと終了カウントを使用してコード部分を囲む場合、2つの違いはそのコード部分の実行にかかった時間になります。
# Measuring the time it takes for worker_function() to run
var start = Time.get_ticks_usec()
worker_function()
var end = Time.get_ticks_usec()
var worker_time = (end-start)/1000000.0
# Measuring the time spent running a calculation over each element of an array
start = Time.get_ticks_usec()
for calc in calculations:
result = pow(2, calc.power) * calc.product
end = Time.get_ticks_usec()
var loop_time = (end-start)/1000000.0
print("Worker time: %s\nLoop time: %s" % [worker_time, loop_time])
経験豊富なプログラマになると、このテクニックは必要なくなります。実行中のプログラムのどの部分が遅いのかがわかり始めます。ループや分岐が遅くなる可能性があることは経験から得られるものであり、測定や調査を行うことで経験を積みます。
ただしプロファイラーとティック関数の間には、コードのどの部分を最適化する必要があるかを見つけるのに十分な情報が必要です。