BGMよ、途切れないで - WSX2を最適化する


にゃーん

昨日から「クッキンアイドル アイ!マイ!まいん!」にハマってしまいました。
第26話まで見たので、だいぶ満足です。
人が生命を維持するために必要な、ロリ成分の年間必要摂取量を満たしたので、もう大丈夫です。
お前の頭は大丈夫か?とか言わないでください。

WSX2を最適化する

(WSX2サウンドエンジンって何?っていう方は http://d.hatena.ne.jp/aoisome/20090705/1246775243 を読んでください)
ところで、WSX2サウンドエンジンをFCCに組み込んだのですが、私のボロい携帯電話だと速度が足りなくて音が途切れがちです。
もうちょっと最適化して高速化できれば、処理が重いソフトでもキレイにBGMが再生できそうなんですが。

グラディウスIIのBGMを途切れずに聞きたいのです。

WSX2のソースコードを最適化すれば、NemulatorPlusなどFCC以外のNESエミュレータにも結果を反映できると思うので、FCCの最適化なんかをやってるよりそっちのほうがよっぽど喜ばれそうです。

そんなわけで、WSX2で最適化できそうな部分が無いか、ソースを読んでいました。
雰囲気的には、G721というADPCM形式へのエンコードを行う部分を中心的に攻めたい感じです。

PCMの1サンプルデータ毎に、G721へエンコードするメソッドを呼び出すようになっているみたいなのですが、G721のエンコード処理は思った以上に複雑な処理のようです。
概算ですが、1サンプルのG721エンコードに、Javaバイトコードで2000命令以上はかかってますね。

エンコード処理が複雑なのはどうしようもないのですが、エンコード処理の呼び出しのオーバーヘッドを減らすことは可能なはずです。
PCMの1サンプル毎にメソッド呼び出しされるのはオーバーヘッドが大きいと思われます。
たとえば、サンプリング周波数が8KHzだったとしたら、1秒間にメソッドが8000回呼ばれるわけです。
G721のエンコード処理の内部でも、小さいメソッドを何回か呼び出しているので、その分も含めると、8000回の何倍かのメソッド呼び出し回数になりますね。

C言語の場合、inline指定をつけたりすれば、コンパイラが空気を読んでメソッドをインライン展開してくれます。
PC用のJavaVMの場合、JITコンパイラがインライン展開してくれます。
そのため、メソッド呼び出しのオーバーヘッドを考えずにコードを組むことができます。
携帯Javaの場合はそうはいきません。
メソッドが自動で展開されることはありません。
すべてはプログラマが書いた通りのコードで実行されるわけですね。

メソッド呼び出しのオーバーヘッドを無くそうと思えば、手でインライン展開してやるしかありません。
はっきりいって、可読性も保守性もへったくれもないコードになっちゃいます。
でも、やるしかないのです。
それが携帯Javaなんです。

じゃあどうするか?

G721のエンコード処理中で呼び出されるメソッドを全部展開しちゃいます。
エンコード処理自体も、1フレームごとに1回だけ呼び出すようにしたいですね。

また、サウンドバッファを毎フレームnewしなおしている作りになっているようなのですが、newは比較的重たい処理ですので、可能であればnewは1度だけにできないか調べてみようと思います。
というよりも、newが重いというより、newがループ中で使われた結果発生するガベージコレクションが重いわけですが。
バッファのnewを何度も繰り返すということは、使われなくなったバッファがどんどん増えていくことになるため、ガベージコレクションを頻繁に発生させてしまいます。

サウンドバッファを2個分最初に用意しておいて、交互に使っていく仕組みにできたらよさそうです。
つまりはダブルバッファリングですね。

また、モジュラ演算(%)や除算(/)をつかっている部分がちょっとありますが、これはそのまま割り算を行うバイトコードコンパイルされるようです。
2の階乗の数で演算する場合なら、モジュラ演算はAND演算に、除算はビットシフト演算に置き換えられるのですが、コンパイラは自動でこの置き換えをやってくれないようです。
この辺をちょっと書き直すと少し速くなるかもです。