問題
createBlock()
は新たなオブジェクトを生成する処理ですが(createPlayer()
も同様)、こういうのはメモリを確保するために若干の処理負荷がかかります。
さらに問題なのは破棄するときで、JavaScript
では、使われなくなった(誰からも参照されなくなった)オブジェクトはガベージコレクション(略して
GC)という機能によって自動的に破棄されますが、これによっても処理負荷がかかります。 参考:
メモリ管理 - JavaScript | MDN
filter()
で毎度新たな配列を作り直している(古いのは GC で破棄している)のも同様です。
今回の例だと気にする必要はありませんが、たとえばスマホ向けにゲームを作ったりする場合、けっこうシビアに考えないと処理が追いつかなくてカクカクしたりします。弾幕シューティングみたいにオブジェクト数が多くてサイクルも速いと、PC
でも怪しい。
GC
でどのオブジェクトが破棄されるかはプログラマには正確に予測・制御できないので、そもそも破棄が行われないように気をつける必要があります。
対策(個々のエンティティの生成破棄)
オブジェクトプールという手法があります。十分な量のオブジェクトをあらかじめ用意しておいて(プール=貯める)、使うときにはプールから取り出して使い終わったらプールに戻す、というやり方でオブジェクトを使い回すというものです。
良い可視化になっているかは分かりませんが、筆者が p5.js で可視化を試みてみたスケッチがこちら:
Object Pool — FAL Works
難点として、
-
あるオブジェクトを使って、使い終わって、また使って……としたときに、
その前後の状態を容易に区別する方法がない
(使うたびに新たなIDを付与して区別することはあるけど、主にデバッグ目的に限る)
-
オブジェクトを使い終わったとき、以下を徹底的にやらないとバグる
- 当該オブジェクトが持っているデータのリセット
- 当該オブジェクトを参照している他のオブジェクトへの通達とその後の処理
以上の理由でこの方法はものすごくバグを出しやすいのですが、背に腹は替えられんという感じでやむなく使ったりします。
オブジェクトプールは配列で実装することになりますが、「使っているオブジェクトのリスト」を別に作るかどうか、別に作らないとしたら使っているオブジェクトをどうやって判別するか、という点で実装上の違いが出てきます。
最も簡易的な方法は、一つ一つのオブジェクトに 生きている/死んでいる
のフラグを持たせるというもので、これは全オブジェクトについて生死のチェックをループのたびに毎回やらなきゃいけないのと、新たにオブジェクトを使うたびに死んでいるオブジェクトを検索しなければいけないというデメリットがありますが、比較的実装は楽だと思います。
他にどんなのが……という詳しい話は書籍『Game Programming Patterns』などが良いかもしれません(サンプルコードは C++ ですが)。
筆者の印象としては、プールをスタック的に扱うやり方が、効率と分かりやすさとのバランスという点で良さそうな気がします。
対策(配列の生成破棄)
Java の
ArrayList
が取っている方法に近いですが、十分な長さの配列と、その長さとは別に「有効な値がいくつ入っているか」を記録しておいて、有効な要素だけを対象としてループ処理をする、という方法があります。
こうすると要素が増減しても配列オブジェクト自体が生成・破棄されることはなくなります。
class ArrayList {
constructor(initialCapacity) {
this.array = new Array(initialCapacity);
this.size = 0;
}
add(element) {
this.array[this.size] = element;
this.size += 1;
}
loop(callback) {
const { array, size } = this;
for (let i = 0; i < size; i += 1) callback(array[i], i, array);
}
// 他、filter() とかは省略
}
こういうデータコンテナ作るの楽しいんですけど、ふと我に返ると、なんでこんなこと手動でやらなきゃいけないんだろうな、という気分にならないでもない。
余談の余談
GC のパフォーマンス問題を経験すると、GC は悪!
という気分になるのですが、本来は無いと困るレベルの便利な機能なので、GC
を悪く言うのは風評被害な感じがあるかもしれませんね。
あと Wikipedia
の項目名だと「ガベージコレクション」となっていますが、本場の発音を考慮すると「ガベージ」ではなく「ガーベジ」や「ガービッジ」ではないか、とかがあるのでこの言葉にはそこそこ表記揺れがあります。
個人的な考えとしては、英語がカタカナに変換されるにあたっては一定の慣習みたいなものがあり、「ガベージ」はそれに則っている雰囲気がするのでこれでもいいと思っています。