FPS制御とGC

きめ細かなFPS制御をしようと思うと、Rubyの処理負荷変動幅の大きさの前に呆然とするしかない。
ガベージコレクタは手ごわい相手だ。
とりあえず、GCの負荷対策への基本姿勢として、以下の3パターンが考えられる。
(a)負荷変動が大きくてもうまいこといく方法を考える。
(b)諦めてそのままにする。
(c)DXRubyでGCの動作を制御する。


(a)は、やるとすればFPSの60→30切り替えをライブラリが制御する、という形になるだろう。
ある程度の負荷を検知したら強制的に30fpsモードとして固定してしまう。
ただこれはゲーム側が制御すればいい話で、あまり気が向かないため要望があればやるかもしれないが、基本的には(b)になると思っている。
ここではアクロバティックな方法として(c)の手段を検討してみよう。


GCはdisableメソッドで止めて、enableで許可することができる。
enableされた状態でstartすれば好きなときにGCを動かせる。
2000オブジェクト動かすプログラムでdisableすると毎回40000台前半のカウントになり、毎フレームstartすると50000台ばっかりになって、時々負荷が増えるのはGCの影響であることがほぼ確定した。
しばらくdisableしてから、enableしてstartして1ループさせて終了すると、最後のループが200000カウントとかの恐ろしい数字になる。
到達不可能オブジェクトがたまりにたまっていて、GCで回収するのに時間がかかるということだろう。
ある程度長い時間動作させるゲームという用途を考えると、動作中ずっとGCをdisableにするという選択肢は考えられない。


Window.loop内でs = s + 1するだけのプログラムを作って、GCをdisableにしといて、enableしてstartして1ループさせて終了すると最後に3000ぐらいの時間を消費する。
ここからわかることは、到達可能オブジェクトの数によってもGC負荷が変わるということだ。
長く動かすとそのぶんちょっとだけ時間がかかるようになるから、sの元の整数オブジェクトが到達不可能オブジェクトになっていると考えられる。
sを文字列で初期化して、s = s + "a"としても同じような感じだ。
"a".to_i.to_sとすると、ちょっとだけGC負荷の増え方が上がる。
to_iしたときの整数オブジェクトが到達不可能オブジェクトとしてたまっていくのだと推測できる。
これらはちょっと遊んでみただけで、FPS制御とはあまり関係ない。
てきとーに動かして感覚でものを言ってるので、信用しないほうがいいかもしれない。
きになる人は自分で動かして確認することをオススメする。


なんしか、このような傾向があるGCに対して、拡張ライブラリ側からこれを制御するとなると、かなり動的なロジックを作る必要がでてきそうだ。
とにかく溜め込むのはやばい。
頻繁に動かすのは、ゲームのロジックの規模によっては非常に遅くなる。
到達不可能オブジェクトが多いほど遅いが、到達可能オブジェクトが多くても遅いのである。
変動している負荷の少ないタイミングで、スキップが発生しないようにGCを動かすというのは、負荷の状態もGCの負荷もロジックや状態によってマチマチであるため、非常に難しい。
GCの負荷が10000前後で、Rubyとdrawの変動は合わせても7000程度だから、少ないタイミングを狙っても吸収できるわけでもない。
そこまで気合を入れて、コマ落ちギリギリを救う必要があるのか、という疑問もある。
苦労の割に効果が少ないような気がしてならない。
実験的に実装してみてもいいが、予想を超えて凄まじい効果が無い限りはリリース版には入らないだろうと思う。