新刊ピックアップ

ゲーム開発で必要となる プロファイリングの基本

この記事を読むのに必要な時間:およそ 3 分

プロファイリングの実行

基本的なCPUサンプリングによるプロファイリングを行います。以下の手順を実行してください。

  1. Visual Studioの[分析]メニューから[パフォーマンスプロファイラー]を選択します。 2.[レポート]ページで[パフォーマンスウィザード]をチェックし,⁠開始]をクリックします。 3.[パフォーマンスウィザード][CPUサンプリング]を選択し,⁠完了]をクリックします。プロファイリングが完了すると,⁠サンプル プロファイル レポート]が表示されます。

CPUサンプリングとは,プログラムの実行中に一定間隔で実行中のスタックトレース(つまりその時点で実行中の関数と,それがどこから呼ばれたかの情報)を記録する方式です。サンプリングによりある関数の出現回数が多いということは,プログラム実行時間全体に対してその関数の実行時間の割合が高いということになります。よって,このサンプリングの統計を見ることで,そのプログラム中のどの関数に時間がかかっているかを調べられます。この方式は完全に正確な測定ではありませんが,プロファイリングによるオーバーヘッドが小さく手軽に行えるため,パフォーマンス解析のはじめの一歩としては使いやすいでしょう。

図1 CPUサンプリングによるプロファイリング結果

図1 CPUサンプリングによるプロファイリング結果

それでは結果を見てみましょう図1⁠。最初[概要]ビューが開かれていると思いますが,⁠ホットパス]はプログラム実行全体の処理時間のうち,どこに時間がかかっているかをツリー形式で表示しています。たとえば,main関数の実行時間(≒プログラム全体の実行時間)のうち,99.06%はfunc1関数の実行時間であり,さらにその99.06%のうちの66.25%はcopy1で,32.81%はcopy2の実行時間であることが分かります。

なお,それぞの関数の実行時間として[包括サンプル][排他サンプル]という項目があります。⁠包括サンプル]はその関数とその関数から呼び出される他の関数の実行時間も含みますが,⁠排他サンプル]はその関数自体の実行時間であり,そこから呼びだされる関数の実行時間は含みません。たとえばfunc1は,それ自体ほとんど処理をしていないので,⁠排他サンプル]は0.00%ですが,重い処理のcopy1とcopy2を呼び出しているため,⁠包括サンプル]はそれらの合計の99.06%になっています。また,copy1とcopy2は他の関数を呼び出さず自身の処理が重いので,⁠排他サンプル]自体が大きく,⁠包括サンプル][排他サンプル]と同じ値です。

一方[最も頻繁に個別の作業を実行している関数]には,プログラム全体の実行時間のうち関数ごとの実行時間が大きい順に並んでいます。ここでいう関数ごとの実行時間とは,その関数から呼び出される他の関数の実行時間は含みません。⁠ホットパス]のところで説明した[排他サンプル]に相当します。なお[ホットパス][最も頻繁に個別の作業を実行している関数]では,同じ[排他サンプル]ですが少しだけ値が違います。これは後者のほうはプログラム実行全体を通した,その関数の実行時間が表示されているためです。具体的には,前者のcopy1の[排他サンプル]は,func1から呼び出されているcopy1の実行時間のみが表示され,後者のcopy1の[排他サンプル]にはmainから呼び出されているcopy1の実行時間も含まれているはずです。

さて,結果を見ると,関数copy1の処理時間がおよそ全体の66%であり,このプログラムのボトルネックになっていることが分かります。まずはこのcopy1の処理速度を改善するのがよいでしょう。なお,関数copy2も同様の処理を行っていますが,copy1はcopy2の2倍くらい時間を使っていることが分かります。このように見ただけでは分からないようなパフォーマンス上の問題を定量的に分析できることがプロファイリングのメリットです(今回のサンプルは恣意的な例なので,実際には見てすぐわかった方も多いかと思います⁠⁠。

copy1の実装はarray of structureを飛び飛びにアクセスしているので効率がよくありません。たとえばcopy2の実装でcopy1を置き換えて再度プロファイリングすると,copy1だけ処理が重いのが改善できることが確認できます。なお,ここでは説明は省きますが,興味のある人は参考文献や"array of structure"をキーワードに調べてみてください。

インストルメンテーションによるプロファイリング

今回の例では,ボトルネックとなっている関数を調べ,その関数の処理速度を改善する最適化について説明しました。しかし関数の処理時間を減らすには,もう一つ方法があります。その関数の呼び出し回数を削減するのです。例えば,ある計算を行う関数に対して,その計算結果を何度も使うために関数呼び出しを連続して行うプログラムはよく見ると思います。これ自体は悪いことではありませんが,処理負荷が高い場合は,最初に関数を呼び出して得られた計算結果を一時変数に保存し,他の関数呼び出しをその変数で置き換えることで,関数呼び出しの回数を削減し,処理速度を改善できます。

それではプログラムの実行を通して関数が何回呼び出されているかはどのように調べればいいでしょうか。Visual Studio Communityのプロファイラではインストルメンテーション方式を使うことで,呼び出し回数を調べられます。インストルメンテーション方式とは,プロファイラが関数呼び出し毎に記録する方式です。正確な呼び出し回数が分析できますが,オーバーヘッドがCPUサンプリングよりも大きく,もともと処理時間のかかるプログラムの解析にはさらに時間がかかる点は注意が必要です。

インストルメンテーション方式は,前述のプロファイリング手順の[パフォーマンスウィザード]で,⁠インストルメンテーション]を選択することで行えます。プロファイリング実行後表示されるページ上部の[現在のビュー]から[関数]を選択してください。関数毎の呼び出し回数が分かります。また[コールツリー]ビューでは,どの関数からどの関数が何回呼び出されているかツリー形式で表示されます。

まとめ

本稿ではVisual Studio Communityによる,CPUサンプリング方式・インストルメンテーション方式のプロファイリングについて説明しました。Visual Studio Communityの登場により,個人開発でもプロファイリングの敷居が下がりました。最適化に活用してみてください。

参考文献

著者プロフィール

長谷川勇はせがわいさむ)

株式会社スクウェア・エニックスにて,Luminous Studio,「FINAL FANTASY XV」の開発に参加し,VFX・UIを担当。専門は言語処理系。学会活動にも参加している。

Twitter:@IsamuHasegawa