メモリプールによる効率的なメモリ使用
この記事では、NALのSE Nguyen Ngoc Tu氏が、メモリプールを使用した、メモリ管理の参考について解説しています。
速度とメモリ使用量のバランスは、プログラマーにとって常に頭の痛い問題です。この問題に直面するとき、プログラマーは速度とメモリのどちらを優先するかを考えなければなりません。長期間稼働するアプリケーションでは、メモリは常に最重要課題の一つです。アプリケーションのライフサイクルの間、オブジェクトは絶えず作成されたり破壊されたりしているため、メモリが消耗してしまいます。メモリ管理が悪いと、リソースを浪費してしまい、システムはすぐにメモリを使い果たしてしまいます。リソースが限られたデバイス向けにプログラミングを行う場合、どちらも重要です。性能とメモリ使用量のバランスをとることは、必ずしも簡単ではありません。
この記事では、NALのSE Nguyen Ngoc Tu氏が、メモリプールを使用した、メモリ管理の参考について解説しています。
メモリプールとは、固定のメモリ領域を初期化し、そのメモリ内のオブジェクトの割り当てやリサイクルを管理することで、メモリを効率的に活用するためのテクニックです。
Poolの仕組みを理解するために、次のような仮定のシナリオを立ててみましょう。
ある映画監督が100人以上のキャラクターがいる台本を受け取りましたが、彼の手元には10人の俳優しかいません。手持ちの俳優が10人しかいない中で、100人以上のキャラクターを使って映画を完成させるにはどうしたらいいでしょうか?この場合の答えは、簡単です:各俳優が異なる役割を演じる、です。
別のシチュエーションを試してみてください:たった10発の弾丸を手に1000機の敵機を破壊するにはどうすればいいのか? : D. それがどれほど実用的なのか疑問に思うかもしれませんが、SkyForceのようなシューティングゲームをプレイしたことがあれば、プログラマーの立場からすればこれは完全に可能です。もちろん、プレイヤーは弾数が10発しかないことを知らずに、ただひたすら撃ちまくります。
この状況は、上記のケースと比較しても、Poolの観点から見ても何ら変わりません。
以下のようにメモリプールの仕組みを説明する簡単なゲームをコード化してみましょう。:1000発の弾丸を搭載したマシンガンを10m先のターゲットに向けて連射していくゲーム。マシンガンが弾切れになるとゲームは終了します。
ここで、このゲームのソースコードを見てみましょう(Javaで書かれていますが、他の言語やプラットフォームにも適用できます)。
1つの基本的なプロパティを持つ弾丸のクラスを時間の経過とともに位置を記述します(m)。
public class Bullet {
private int position;
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public Bullet() {
}
public void move() {
position++;
}
}
メモリープールクラス(box2d.orgのメモリープールのソースコード)では、特定のオブジェクトグループのプールを作成することができます。
import java.util.LinkedList;
public abstract class MemoryPool<T> {
private LinkedList<T>; free_items = new LinkedList<T>();
public void freeItem(T item) {
free_items.add(item);
}
protected abstract T allocate();
public T newItem() {
T out = null;
if ( free_items.size() == 0 ) {
out = allocate();
} else {
out = free_items.getFirst();
free_items.removeFirst();
}
return out;
}
}
メモリープールがFreeListデータ構造を使用していることがわかりますが、ここではfree_itemsは役割の終了時に解放されたオブジェクトを格納するリンクリストであり、newItem()メソッドで再利用されます。メモリープールは、newItem()のように作成して再利用するのではなく、実際のオブジェクトを新たに作成するためのメソッドallocate()を持っています。
BulletPoolクラスはメモリープールで、以下のように実装されています:
public class BulletPool extends MemoryPool<Bullet> {
@Override
protected Bullet allocate() {
return new Bullet();
}
}
経済的に撃つ(プールを使う)か、惜しげもなく撃つ(プールを使わない)かの能力を持つ機関銃を描写したガンクラス。
public class Gun {
private int bulletCount=1000;
public void fireInPool() {
BulletPool pool=new BulletPool();
List<Bullet>; plist=new ArrayList<>();
for(int i=0;i<bulletCount;i++) {
Bullet p=pool.newItem();
p.setPosition(0);
plist.add(p);
for(int j=0;j<plist.size();j++) {
Bullet pp=plist.get(j);
pp.move();
System.out.print("-"+pp.getPosition());
if(pp.getPosition()==10) {
pool.freeItem(pp);
plist.remove(pp);
}
}
System.out.println();
}
}
public void fire() {
List<Bullet> plist=new ArrayList<>();
for(int i=0;i<bulletCount;i++) {
Bullet p=new Bullet();
p.setPosition(0);
plist.add(p);
for(int j=0;j<plist.size();j++) {
Bullet pp=plist.get(j);
pp.move();
System.out.print("-"+pp.getPosition());
if(pp.getPosition()==10) {
plist.remove(pp);
}
}
System.out.println();
}
}
}
最終的にゲームプレイ。
public class DemoMemoryPool {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Gun gun=new Gun();
System.out.println("Start");
gun.fireInPool();
gun.fire();
System.out.println("Game over");
}
}
ここで、Profilerを使ってBulletオブジェクトのメモリ使用量を測定してみます。結果は非常にはっきりとわかります。Poolを使って1000発の弾丸を発射しない場合、1000個のBulletオブジェクトを初期化する必要がありますが、Poolを使った場合は11個のオブジェクトを初期化するだけで済みます。
興味深いことに、この11個のオブジェクトは反復回数に依存せず、オブジェクトを解放する条件に依存します。(この例では、位置 = 10)
このように、Poolを巧妙に使用することで、アプリケーション、特にゲームやリアルタイムのインタラクティブなアプリケーションのメモリ制限を徹底的に解決できることがわかります。もちろん、プールの実装もまた、プラットフォームや特定の問題に依存します。うまくいけば、この記事を通して、プログラマーは日常的な問題に対してもう一つの解決策とアイデアを持つことになるでしょう
引用: https://media.nal.vn/efficient-memory-usage-with-memory-pool