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

アバター
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 回目 ]

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

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

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

CBullet* p;
みたいなもの用意して、アクセスする度にp++;をするってことですか?
計算が入る分余計に遅くなりません?
簡単なコードの実装状態を教えて下さいね。
then節ってなんです・・?
あっとC/C++なら真になる条件の確率が高くなるようにして下さいということです。
Pascal/FORTRAN/BASICだとそう言うので、つい・・・。

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

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

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

> softyaさん

あ~例えば構造体配列だとすると

CODE:

for( int i=0; iflag;
}
って参照の方が速いってことですか?

> then節

なるほど。

CODE:

if( ... ){
    proc1();
} else {
    proc2();
}
これはproc1()が呼ばれる可能性が高いようにした方がよいということですか?
最後に編集したユーザー Dixq (管理人) on 2011年9月13日(火) 00:31 [ 編集 1 回目 ]

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

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

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

CODE:

Bullet_t* p=BulList;
for( int i=0; iflag;
}
2~3つ以上参照すると速いはずです。
あと

CODE:

Bullet_t* p=BulList;
for( int i=0; iflag;
}
じゃないと意味が無いですね。
これはproc1()が呼ばれる可能性が高いようにした方がよいということですか?
thenの確率が高いなら、elseが無いほうが速いと言う話ですね。
場合によってですが、

CODE:

if( x< 10 ) {
 a=1;
} else {
 a=0;
}
よりも

CODE:

a=0;
if( x<10 ) {
 a=1;
}
が速いはずですが、昨今のコンパイラの最適化とCPUのパイプラインの挙動までは把握しきれてません。

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

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

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

なるほど。
しかし上の場合、「p++」という余計なインクリメント計算が入りますが、それでもなお速いということですか?
実際の測定は明日やってみようと思います。

ISLe
記事: 2650
登録日時: 14年前

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

投稿記事 by ISLe » 14年前

配列の高速アクセスはこうですね。

CODE:

Bullet_t* p=BulList;
Bullet_t* end=&BulList[NUM];
for( ; pflag;
}
でもリストを活用したほうが速いですよ。
使用中弾リストを作れば弾の存在確認自体が要らなくなります。
他の弾に依存する処理も依存弾リストを持たせれば良いです。
メモリプールも、事前に確保したメモリをリスト管理して、オーバーライドしたnewで使用していないメモリのリストから取り出して使用しているリストに移動、オーバーライドしたdeleteで使用しているリストから使用していないリストに移動、って感じに。
最後に編集したユーザー ISLe on 2011年9月13日(火) 01:24 [ 編集 1 回目 ]

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前

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

投稿記事 by h2so5 » 14年前

Dixq (管理人) さんが書きました:>パコネコさん

リストはありだと思うんですが、弾幕って何かの弾に依存した動きする時、特定の弾にアクセスしたい時ありません?
4000個ある内の2000番目の弾とか・・。
そうなるとリストって一気にアクセスが遅くなるんじゃないかと思います。
弾幕のプログラムは書いたことがないですが、
自分だったら特定の弾に関連した動きをさせたい場合は弾を生成した時点で、
弾自体に他の弾へのポインタを持たせる方法を使うと思います。
そうすればlistへのランダムアクセスは発生しないので。

アバター
tk-xleader
記事: 158
登録日時: 14年前

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

投稿記事 by tk-xleader » 14年前

内部で頻繁にアロケートしたりしてもvectorが遅くなるとは思います。通常、push_backする前にreserveしておくのが定石ですが…
VisualC++では、最適化をかければvectorは大抵の関数がインライン展開されて普通に動的配列使うのと全く同一のコード、あるいはそれに近いコードが生成されるはずです。

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

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

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

> ISLeさん
> カロさん

>他の弾に依存する処理も依存弾リストを持たせれば良いです。

依存した弾のポインタを持たせるということですか?
同じ実体を示すポインタが複数あるというのはあまり・・な気がするんですが、私の認識違いでしょうか。
実体deleteしたのに、参照しちゃうとか。

> tkさん

vectorでpush_backは使ってないですね~。なのでatも使わないし、特にvector化する必要性も感じてないです。

ISLe
記事: 2650
登録日時: 14年前

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

投稿記事 by ISLe » 14年前

Dixq (管理人) さんが書きました:同じ実体を示すポインタが複数あるというのはあまり・・な気がするんですが、私の認識違いでしょうか。
実体deleteしたのに、参照しちゃうとか。
そういうときのためのスマートポインタですよ。
参照されなくなったらオブジェクト自身がdeleteする設計にすると、ダングリングを回避できます。

(追記)
まだ参照されているのに実体が削除できてしまうのは問題なので、参照が無くなるまでオブジェクトの破棄が保留されるようにすべきです。
参照する側とされる側をどちらから削除するかという順番を気にしなくても良くなるというメリットもあります。
最後に編集したユーザー ISLe on 2011年9月14日(水) 22:43 [ 編集 2 回目 ]

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

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

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

> ISLeさん

なるほど・・・。今までの疑問がすごく晴れました。。。
まだまだC++初心者なので、情報ありがたいです。
早速あちこち修正してきます。