Cによる高速化とRuby

DXRubyFrameworkはゲームの基本ロジックをCに完全に閉じ込めることで圧倒的な速度を叩き出そうという試みだ。
Spriteクラスを継承してクラスを作り、オブジェクト生成時の初期化で各種設定をして、あとは勝手に動く。
設定に応じて必要なときだけRubyのメソッドが呼ばれるから、直進する弾程度なら完全にCだけで動作するようになる。
ただ、この「完全にCだけ」というのが簡単そうで意外に難しい。


Rubyの遅いところは第一にメソッド検索である。
動的言語の大きな弱点であり、更にRubyではメソッドを動的に追加したり削除したりできるため、この検索処理を削る手は無い。
メソッド検索が発生する関数をCから呼んでいては速度低下は免れないわけだ。
すなわち、rb_funcall系、rb_iv_get、rb_iv_set。
これを使わないようにすれば、相当な速度が出せる。
1キャラ動かすのに1回呼ぶだけでガクンと遅くなる。
極限まで速くするとわずかな負荷で大きな影響が出るから、非常にデリケートなものになる。
当然、ゲームを作ろうとするとある程度はRubyの処理が入ってくるから(だってRubyでゲーム作るんだし)、処理速度がRubyのコード量と反比例するようなグラフになるのなら、極限の速度はあまり関係ないのかもしれない。
とはいえ弾幕ゲーを作ろうとしたときに、500を超えるオブジェクト全てにRuby処理が入っていては無茶な話になるわけで、完全にCだけで作られたロジックでシンプルな弾ぐらいは動いてくれないと困る。


昨日の記事の時点では衝突判定処理をDXRubyExtensionでやっていた。
が、Spriteのデータの中に衝突判定オブジェクトを持たせると、その座標を移動するのにsetメソッドを呼ばないといけない。
DXRubyExtensionのウリは違う形状の衝突判定ができるところだから、それを使うなら衝突判定オブジェクトのsetメソッドはいわゆるダックタイピング状態で呼ぶことになる。
ここでrb_funcallが必要になり、かなり遅くなっていた。
さっき、衝突判定を矩形のみに変更し、DXRubyExtensionを使わないようにして自前で判定するように変えたところ、負荷30%で1500個ぐらい動くようになった。
そのぐらいの差が出ると考えると、Rubyのメソッド検索を完全に無くすのは、弾幕ゲーを作るという目標のためには絶対に必要なことだろう。


rb_iv_get、rb_iv_setが使えないとインスタンス変数を扱うことができない。
だからDXRubyFrameworkではSpriteクラスはインスタンス変数を持たない。
すべてメソッド呼び出しで設定する。
rb_funcallが使えないと色々な弊害が出る。
たとえば衝突判定は、Ruby的に考えれば衝突判定メソッドをincludeするようにする感じがよいかもしれないが、そうすると判定処理にrb_funcallが必須になる。
DXRubyExtensionがモジュールじゃなくクラスになっていて使いにくいのは、判定を完全にCに閉じ込めて高速処理するためだ。
俺が目指すのは「Rubyで書くよりちょっと速い」じゃなく、「圧倒的に速い」。
幸い、DXRubyは処理をCだけで書いた場合に相当な速度が出てくれているから、努力する価値がある。


ただ、こういうのを作ってリリースなんかすると、DXRubyというのは本当に初心者向けなのかどうかという点で怪しくなってくる。
怪しいもなにもそれは大きな勘違いである。
俺は俺用にソフトを書いているのであって、はなから初心者向けに作っているわけではない。
簡単に使えるように作ったDXRubyが初心者にもよさそうだから初心者向けとしてリリースしただけで、今後作るほかのものも全て初心者をターゲットしていくという意味ではないのだ。
使いやすくはないだろうし、Rubyぽい設計にもならないだろうとは思うが、Rubyの限界についての一般的な認識はぶち壊すつもりでいる。
そういうものが作れるという可能性を示す意味は大きいと思う。
あとは誰かがRubyっぽく使いやすくて速いものを作ってくれれば。


ところで、Spriteクラスのクラスメソッドに、配列を渡してupdateするとかdrawするとか衝突判定するとかのメソッドを作ってしまっていて、ゲーム用総合フレームワークというよりSprite管理ライブラリになってきている。
名前をDXRubySpriteに変更したほうがいいかもしれない。
ゲーム用のフレームワークなんてやっぱり作れない気がする。
だって俺ゲーム作ったことないし、フレームワークに必要な機能がわからないから。