これは全然発見じゃない。弾幕の設計

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 14年前
住所: 北海道札幌市
連絡を取る:

これは全然発見じゃない。弾幕の設計

投稿記事 by Dixq (管理人) » 14年前

今回作っているSTGは完全にオブジェクト指向で「いかに適切な設計をするか」を求めようと思っています。

■お詫びと訂正■
vectorをoperator[]でアクセスし、flag参照関数をinline化し、最適化かけたら同じ速度になりました。。。


汎用的・拡張性の高い(便利な)設計は、実行効率や速度が低下しがちです。
しかし、リソースが豊富な現在、実行効率を少々犠牲にしても、便利な設計を重視する方が良いという考え方もあります。
そこで、便利な設計⇔実行効率のトレードオフをどの辺で判断すべきか、検証してみました。

弾幕ゲー制作の中で一番肝になるのが、「弾」データの持ち方。
弾データは弾クラスの一つのオブジェクトですが、持ち方は以下の4通り位あるでしょう。



・メモリプールから確保できる形でlistを使う(new,deleteを繰り返す弾オブジェクトのリスト)
・タスクリストを使う
・vectorを使う
・配列を使う



実行速度は上ほど遅い、下ほど早いと思われます。(vectorは安全に.at(i)でアクセス)

まずは、配列とvectorでどれ位変わるか調べてみました。

約5万個の弾を出した時の計算時間

[vector編]
画像
計算部 = 915[ms]
描画部 = 27[ms]

[配列編]
画像
計算部 = 267[ms]
描画部 = 16[ms]

なんと!vectorは配列の約3.5倍!
かなり処理に時間がかかるようです。
あらゆるアクセスに関数コールが生じるのがボトルネックのようです。(特にat?)
そこで、関数コールをなるべくしないように考えます。

弾クラスにはその弾が使用中かどうかを表すフラグ変数がありますが、この変数はprivateなので

bool IsUsing();

みたいな関数を通じてしか使用中かどうかチェック出来ません。
しかし、膨大な回数のこの関数コールが生じています。そこで、フラグ変数をpublicにし、直接参照するようにし、配列版をさらに高速に計算出来るように試みました。
すると・・・

[配列+関数コール削減版]
画像
計算部 = 49ms
描画部 = 16ms

計算時間が約1/18になったではないですか!
どうも関数コールは相当なボトルネックになるようです。
とは言え、汎用性や拡張性を意識すると、どうしても関数に細分化しがちです。
この辺のトレードオフは基準が難しそうですが、頻繁にコールする関数はなるべく作らないように設計するのが良いようです。
(ちなみに上記関数はinlineにしても変化ありませんでした。使い方が悪いのかな・・)
※追記:最適化かかってないだけでした;

もっとこうすればいいとかあれば、何かご意見もらえると嬉しいです!
最後に編集したユーザー Dixq (管理人) on 2011年9月13日(火) 01:02 [ 編集 8 回目 ]

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 14年前
住所: 北海道札幌市
連絡を取る:

Re: これは発見!弾幕の設計

投稿記事 by Dixq (管理人) » 14年前

ちなみに、龍神録では最大4000個の弾が出現します。(絵弾幕等)
それを考えると、この実行速度の差は目をつぶれない・・。配列にして、関数コールを減らすという方針で決まりでしょうか・・?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前

Re: これは発見!弾幕の設計

投稿記事 by softya(ソフト屋) » 14年前

今のコンパイラで速いかどうか分かりませんが、配列で添字を使わずにポインタで操作したら早くなりませんか?
[補足]徹底的に添字は使わないようにしてみてください。
最後に編集したユーザー softya(ソフト屋) on 2011年9月12日(月) 22:03 [ 編集 1 回目 ]

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前

Re: これは発見!弾幕の設計

投稿記事 by softya(ソフト屋) » 14年前

まぁ、最適化されて関係ない可能性が高いんですけどね。
最後の手段はアセンブラコードを見るしか無いって事になります。

あと思いつくのは、
(1)switchはある程度数が多くなるとテーブルジャンプに置き換わるはずなので、パターンの多い場合は積極的に活用してください。
(2)分岐は極力減らしてください。if文だけでなく条件演算や3項演算子とかもですね。出来るだけストレートな計算で処理できるようにします。
(3)分岐はthen節に行く可能性が出来るだけ高くなるように設計してください。
あたりの所でしょうか。

アバター
SAI
記事: 115
登録日時: 14年前

Re: これは発見!弾幕の設計

投稿記事 by SAI » 14年前

とても興味深いです!
配列でも関数コールで時間がかかるのならpublicにしてしまうというのもありかもしれませんね。
配列の場合、特に弾幕STGとなると最低でも弾の計算、描画のフラグチェックに弾の最大数×2回フラグチェック関数を呼ばないといけないので重要そうですね。
私のゲームでも検討してみたいと思います。

xxx
記事: 26
登録日時: 14年前

Re: これは発見!弾幕の設計

投稿記事 by xxx » 14年前

>(1)switchはある程度数が多くなるとテーブルジャンプに置き換わるはずなので、パターンの多い場合は積極的に活用してください。
二分木に展開されると思ってたんですがこういうのもあるんですか?

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 14年前
住所: 北海道札幌市
連絡を取る:

Re: これは発見!弾幕の設計

投稿記事 by Dixq (管理人) » 14年前

> softyaさん

アドバイスありがとうございます。

> 配列で添字を使わずにポインタで操作したら早くなりませんか?

それって
CBullet* p;
みたいなもの用意して、アクセスする度にp++;をするってことですか?
計算が入る分余計に遅くなりません?

> 最後の手段はアセンブラコードを見るしか無いって事になります。

まぁそこまでやる気はないです^^;
微々たる違いは、分かり易い設計になるようにシフトした方がずっと良いと思ってます。
今回のように1/〇〇になるとかなら別ですが・・。

> (3)分岐はthen節に行く可能性が出来るだけ高くなるように設計してください。

then節ってなんです・・?

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 14年前
住所: 北海道札幌市
連絡を取る:

Re: これは発見!弾幕の設計

投稿記事 by Dixq (管理人) » 14年前

> SAIさん

すみません!
追記に書いた通りなんですが、最適化かかってなかっただけでした。
積極的に関数化し、関数をinlineにするのが良いと思います。

アバター
パコネコ
記事: 139
登録日時: 14年前

Re: これは発見!弾幕の設計

投稿記事 by パコネコ » 14年前

ありゃ、最近リストを覚えたので、次にSTG作るときには使用しようと思ってましたが、どうやらかなりの数を処理させなければならない時には向かないみたいですね。
(特に弾幕w)

それにしてもおぞましい数ですね。
ですが、処理速度を早くしようとすると、関数を少なく(もしくは大きく)しないといけないんですね。
勉強になります。
=====================
添え字使わないポインタ操作って
p[10000];
x++;
*(p+x);
見たいなのでしょうか?
最後に編集したユーザー パコネコ on 2011年9月12日(月) 22:32 [ 編集 1 回目 ]

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 14年前
住所: 北海道札幌市
連絡を取る:

Re: これは発見!弾幕の設計

投稿記事 by Dixq (管理人) » 14年前

>パコネコさん

リストはありだと思うんですが、弾幕って何かの弾に依存した動きする時、特定の弾にアクセスしたい時ありません?
4000個ある内の2000番目の弾とか・・。
そうなるとリストって一気にアクセスが遅くなるんじゃないかと思います。
そこまで弾の量が多くなく、特定の弾へのアクセスも無いなら、リストが良いと思いますよ。
ただ、メモリプールしないといけないから、結局メモリは最初にガッツリ取っちゃうし、あんま恩恵無いかもしれませんが・・。

アバター
a5ua
記事: 199
登録日時: 14年前

Re: これは発見!弾幕の設計

投稿記事 by a5ua » 14年前

範囲外にアクセスしないことがわかっているなら、atでの範囲チェックは無駄になると思うのですが、
vectorでoperator[]を使った場合は、どうなるか気になります。