はじめに
ゲーム開発においてメモリ割り当てコストが気になったので検証・勉強もかねて独自のメモリアロケータを実装しました。
設計
- メモリ割り当て時間の短縮を重視
- 固定長のメモリブロックをあらかじめ確保しておき、メモリ確保時に適切なブロック領域から割り当てを行う。
- ブロックサイズ8,16,32,64,128,256 (バイト)
- ブロック数各1024個。オーバーしたら自動で拡張
- 257バイト以上は通常のmallocを呼び出す
- 未割当領域のアドレスをスタックで管理を行う。
実装
プロジェクト一式
Github
簡潔に書くとこのような実装方法です。
int blockSize = 8;
int blockNum = 1024;
void* memoryPool = new char[blockSize * blockNum];
Stack<void*> unUsedPoolStack;
for(int i = 0; i < blockNum; i++){
//blockSize(8バイト)ごとにアドレスをStackに詰める
unUsedPoolStack.push(memoryPool + i * blockSize);
}
void *operator new (size_t size) {
void* p = nullptr;
if(size <= 8){
//Stackからアドレスを取り出し
p = unUsedPoolStack.top();
unUsedPoolStack.pop();
}
return p;
}
void operator delete (void* p, size_t size) {
if(size <= 8){
//使い終わったのでStackにアドレスを追加
unUsedPoolStack.push(p);
}
}
結果
通常のnewと自作のnewでの計測結果
12バイトのクラスを100万個割り当てを行い、それを10回繰り返した平均時間から実行速度割合を算出しました。
newTimeRate:newのみを100万回実行した平均
deleteTimeRate:deleteのみを100万回実行した平均
new/deleteTimeRate:newとdeleteを100万回実行した平均
Debug実行時
newTimeRate:51.1945%
deleteTimeRate:33.0951%
new/deleteTimeRate:43.4589%
Release実行時
newTimeRate:19.1974%
deleteTimeRate:7.625%
new/deleteTimeRate:7.99777%
上記からnewだけ見てもDebug時で2倍、Release時で5倍の速度が出せることがわかり、
200行程度のコードでこれだけ早くなったので満足いく結果になりました!
ただ、std::allocatorは実装していないためSTLにも適応する場合は追加実装が必要です。