メモリプール(1)

先日書きましたが、C++ではガベージコレクタがないためにフラグメンテーションが発生して速度の低下をもたらすことがあります。また、同じオブジェクトを大量にnew/deleteするような場合、メモリアクセスの時間もパフォーマンスに影響してしまいます。そこで、メモリプールとかアリーナという考え方があります。要するに、一気にまとまったバッファを取得してしまい、newするときはそのバッファから順にメモリを割り当てて使う考え方です。

以下は、単純なメモリプールクラスのコードです。次のようなケースを前提にしています。

  • ある処理をする時にのみオブジェクトをどんどん生成する。
  • その処理が終わったら一気に開放する。
  • その処理は、全体の処理中に何度か行われる。

ちょっと考えれば分かりますが、このケースではフラグメンテーションはほとんど起こりません*1。どちらかといえば、new/deleteにからむパフォーマンスの向上に用いるべきコードです。


/*
* Memory pool class.
*
* メモリをどんどん使って一気に解放するモジュール専用
*/

class igaMemoryPoolBody {
/*
* バッファの本体
* 単方向リンクリストをなしている
*/

private:
igaMemoryPoolBody *next;
char *buffer;
int pos, obj_size, num_of_item;

public:
igaMemoryPoolBody(int osize, int inum)
{
obj_size = osize;
num_of_item = inum;
buffer = new char[obj_size * num_of_item];
pos = 0;
next = (igaMemoryPoolBody*)NULL;
}
~igaMemoryPoolBody()
{
if (next != (igaMemoryPoolBody*)NULL){
delete next;
}
delete [] buffer;
}

inline void *get_ptr(){
/*
* 利用可能なポインタを返しポインタをインクリメントする
* バッファからあふれた場合はNULLを返す
*/
if (++pos >= num_of_item) return (void*)NULL;
return &(buffer[obj_size * (pos - 1)]);
}
inline void reset(){
/*
* 初期化する
* バッファの中身はすべて破棄されるが
* 同じ領域を使い回すだけなのでポインタを戻すだけ
*/
pos = 0;
}
inline igaMemoryPoolBody *grow(){
/*
* this->nextを初期化された状態で返す
*/
if (this->next == NULL){
this->next = new igaMemoryPoolBody(obj_size, num_of_item);
} else {
this->next->reset();
}
return this->next;
}
};

class igaMemoryPool {

private:
igaMemoryPoolBody *top_buffer, *current_buffer;

public:
igaMemoryPool(int osize, int inum)
{
top_buffer = new igaMemoryPoolBody(osize, inum);
current_buffer = top_buffer;
}
~igaMemoryPool()
{
delete top_buffer;
}

inline void *alloc()
{
/*
* オブジェクトを配置するアドレスを返す
* バッファの管理も行う
*/
void *ret_ptr = current_buffer->get_ptr();
if (ret_ptr == NULL){
current_buffer = current_buffer->grow();
ret_ptr = current_buffer->get_ptr();
}
return ret_ptr;
}
inline void reset()
{
/*
* ポインタを先頭に戻す
*/
current_buffer = top_buffer;
top_buffer->reset();
}
};

igaMemoryPoolクラスがバッファの統括をするクラスで、igaMemoryPoolBodyクラスが実際にオブジェクトが配置されるバッファになります。単純な作りにしてあるので色々問題もあると思われますが*2

具体的にどのように使用するかはまた今度。

*1:並列的に処理をするなら分かりませんが。

*2:例えば、一旦取得したメモリはigaMemoryPoolクラスがdeleteされない限りずっと開放されない点。閾値を設けておき、その値以上リンクリストが増えたら開放するというほうが良いかも知れません。他の処理をする時にメモリ不足となる場合が考えられますからね。