ベンチマーク
今回はいかにも自作PCっぽい話題と見せかけて、今作ってるゲームの話です。
今作っているゲーム(あやかし紅白戦)ですが、以前から一つの懸念がありました。
それは……
重くね?
ってことです。
当サークルのゲームは基本的にドット絵2Dなので、パッと見レトロな雰囲気です。あくまでパッと見。
別に色数や解像度を昔のハードに合わせて縛り製作ているわけでもないし、内部的には3D系の処理をしている部分も多いので全然レトロじゃないんですが「こんな古臭いゲームがなんでここまで重いの?」という血も涙も無い評価を頂く事があります。主にリアル友人。
3D系は多少重くてもは許されて、2D系には厳しい風潮……ありますよね……。
実際問題として、開発初期は何の問題もありませんが、製作が進むにつれていろいろな要素を入れたくなってしまい、しかもその要素を「出来るか分からないけど取りあえず作ってよう!」と効率や高速というものをあまり考慮せず、シンプルに実装できるやり方でどんどん追加していった結果、いろいろな場所に無駄な処理が入り込んでしまう(ように感じる)のです。
さらに安全に動作するようにさまざまなチェックを入れていった結果、メモリーやポインタ関係のエラーは格段に減ったものの、代償として処理速度がガンガン削られることとなりました。
デバッグモードで動かした場合は本当にひどくて、マップによっては重すぎでテストする気がなくなるぐらいです。
(ヴィータ大脱出の頃はデバッグモードで動かしてもそこそこ早かったんですよ……)
高速化の手段と言いますか、引っ掛かってそうな部分はたくさんあるので、そこを片っ端から直しても良いのですが、80:20の法則(プログラム全体の2割の部分で、全体の処理時間の8割を消費している的なヤツ)のこともありますし、ここはきちんとプロファイラー(プログラムのどこでどれだけの時間を消費しているか調べるヤツ)を使って、どこがホットスポットになっているか調べた方が良いなと。
最近の Visual Studio は無料版にもプロファイラーがついてますしね。
以下、プロファイラーについての話がしばらく続きます。面倒でしたら後半までスキップしちゃってください
ここから細かい話
1分ほど起動した後に終了すると、こんな感じで解析結果を出してくれます。
これを見ると、重い割には CPU 使用率が10%程度なのですが、これは即ちマルチコアの1個のCPUだけがめちゃくちゃ頑張ってるっていう状態ですね。
分散処理とか何もしてないので……。
で関数の利用状況の表を見てみると、こんな感じになるわけですよ
この「セルフCPU」という項目を見ると mo::CCollisionImpl::update_dynamicbody_collision_unsafe 関数で(名前が見切れてますが)全体の 24% の時間を消費していることになっています。
仮にこの関数の実行時間が 0% になったら、全体の実行時間が 100 → 76 に減ることになるので全体では 1.3 倍程度早くなるってことですね。
※合計CPUは、その関数の最初から最後までの合計実行時間です。
別の関数を呼んで、その関数が戻ってくるまで待っているだけの時間もすべてカウントしています。
例えば以下のようなプログラムの場合、 main 関数の合計CPUはほとんど100%になります。
int main() {
game_main();
return 0;
}
それに対してセルフCPUは、純粋にその関数自身が実行している部分の時間です。なので、別の関数を呼んだ場合、それが戻ってくるまでの待ち時間は含みません。
上記の main 関数で言えば、main 関数はただ game_main() を呼んでいるだけで、main 関数自身は何もしていません(戻り値 0 をセットするぐらいか)。この場合のセルフCPUは 0% になります。
より詳しく見れば、具体的に関数のどこの部分でどれだけの時間を消費してるかってのが分かります。
例えばこの関数 get_active_static_body_list_unsafe で言えば
15554行目にある body-node->is_static() の呼び出しだけで全体の 11.81% の時間を使ってるとあります。
なので、このあたりをどうにかすれば速度が改善しそうだ、となるわけです。
ちなみに関数の呼び出し関係を表示させることもできて、これを使うと、同じ関数がどこから呼び出されたときに一番時間がかかっているかを見ることができます。
現在の関数に mo::KCollider::get_aabb とありますが、この関数の呼び出し元が左にあります。これをみると、mo::CCollisionImpl::update_dynamicbody_collision_unsafe から呼ばれたときに 11.72% の時間を消費している、とあります。
また、 mo::KCollider::get_aabb が呼び出した関数の中では KMatrix4::transform が一番時間を消費しています(真っ赤になってます)
あきらかに行列計算で持っていかれてますね……(これでもSIMD使うようになって少しは改善したんですよ)。
※ちなみにこの画像の下に表示されてる情報だと get_offset_world() が 9.54% 消費してることになっていますね。
この関数は上のリストにありませんが、これは最適化によって get_offset_world の呼び出しが消え、本当なら get_offset_world の中から呼ばれていた KMatrix4::transform が直接呼ばれているということです。
細かい話ここまで
まあそんな感じで、プロファイラーつきでゲームを起動して重そうな場所に目星をつけ、そこを修正してもう一度起動し、改善されたかどうか見てみる、というのを繰り返していたわけです。
ところでこのとき、ロード画面やタイトル画面をプロファイルしてもあまり意味はないので、
ゲームを起動→ロードが終わってタイトル画面が出るまで待つ→タイトル画面放置デモを起動させる(プレイヤーを自動操縦させたいので)→デモ部分でのCPU利用状況を調べる
という手順を踏んでいしましたが、さすがにこれを毎回毎回やるのが面倒になってきたので、起動してすぐに自動でデモモードが始まるようにしたものを作りました。
起動すれば後は勝手にステージが始まってプレイヤーが自動で動き回るし、ステージに必要なデータしかロードしない(タイトル用のデータとかいらない)ので起動が若干早くて楽になるというものです。
……で、何となくこれを眺めていてい思ったんですよ。
「これ、スコアを付ければそのままベンチマークソフトになるんじゃね?」と。
これを体験版に内蔵しておけば、動作快適環境の目安にもなりますよね。
というわけでメニューとか作って、紆余曲折を経て、ひたすら敵が湧くだけのベンチマークとして体裁を整えてみました。
今回は低負荷だけ作りましたが、将来的に高負荷の方ではムチャクチャやってみたいと思います。
……毎度のことですが、どうしてこういうことに手が伸びるんでしょうね?
そんな暇があったらゲーム本編を(略
フォロワー以上の特典でそのベンチマークソフトがダウンロードできます。
気になる方はどうぞ。(フォローだけなら無料です)
フォロワー以上限定無料
まずは無料プランで様子見を。 お気軽にフォローしてみて下さい。
無料