引言
陈硕老师曾经说过大部分情况下自己实现一个内存池是没有必要的,我们要相信malloc的作者。但是leveldb的作者也不是凡夫俗子(去google jeff dean 网 上 有 真 相)。leveldb为什么自己实现一个内存池我相信一定有它的道理,虽然这篇文章不大可能道出作者的精妙想法,但还是试着解释清楚程序的基本思路和框架。
基本思想
为什么不用new或者malloc分配内存?
- 频繁地分配内存,造成内存碎片化
- new完忘记delete,造成内存泄漏
leveldb的做法:先向系统申请一块大的内存,外部需要申请内存时,先把已有的内存块分配给用户,如果不够用则再申请一块大的内存。当内存池对象析构时,分配的内存均被释放,保证了内存不会泄漏。
源码分析
/util/arena.h
和/util/arena.cc
包含了内存池的定义和实现。以下是Arena类的声明。
声明
public:
Arena();//构造函数
~Arena();//析构函数
char* Allocate(size_t bytes);//分配"bytes"大小的空间并返回指向该地址的指针
char* AllocateAligned(size_t bytes);//分配“byte”大小的空间,保证字对齐。
size_t MemoryUsage() const//返回已分配内存的大小(不精确)
private:
char* AllocateFallback(size_t bytes);//当前内存块的剩余容量不足以支撑所申请内存时,调用此函数
char* AllocateNewBlock(size_t block_bytes);//使用malloc分配一个新的内存块
// Allocation state
char* alloc_ptr_;//指向当前内存块剩余空间的地址
size_t alloc_bytes_remaining_;//当前内存块剩余容量
std::vector<char*> blocks_;//存放指向内存块地址的指针,用于析构函数中释放内存
size_t blocks_memory_;//已分配内存大小
Arena(const Arena&);//只申明不实现,防止编译器隐式拷贝,详情可自行查询google c++ style
void operator=(const Arena&);//同上
实现
char* Arena::AllocateFallback(size_t bytes) {
if (bytes > kBlockSize / 4) {
char* result = AllocateNewBlock(bytes);
return result;
}
alloc_ptr_ = AllocateNewBlock(kBlockSize);
alloc_bytes_remaining_ = kBlockSize;
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
如果申请内存大于块大小的1/4,我们将单独为其申请一块大小正好的新内存块,防止内存块的其他空间被浪费。否则我们将申请固定大小的内存块,并修改状态。
char* Arena::AllocateAligned(size_t bytes) {
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
//指针大小,由编译器产生的目标平台的指令集决定。譬如说x86就是4,x64就是8 by vczh
assert((align & (align-1)) == 0); //确保指针大小为2指数倍
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);//强制类型转换
size_t slop = (current_mod == 0 ? 0 : align - current_mod);
//可以看作是内存的空隙
size_t needed = bytes + slop;//填满空隙后所需内存大小
char* result;
if (needed <= alloc_bytes_remaining_) {
result = alloc_ptr_ + slop;//从对齐处开始分配内存
alloc_ptr_ += needed;
alloc_bytes_remaining_ -= needed;
} else {
//不需要再添空隙,因为该函数永远对齐
result = AllocateFallback(bytes);
}
assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
return result;
}
reinterpret_cast
为暴力转换,请慎用。
最后看一下MemoryUsage的实现,
size_t MemoryUsage() const {
return blocks_memory_ + blocks_.capacity() * sizeof(char*);
}
返回已分配内存块的大小以及指向内存块指针的大小,忽略了Arena其他数据成员的大小,如alloc_ptr_
,alloc_bytes_remaining_
等。
总结
不推荐在实际工程中自己实现一个内存池,因为内置的malloc在一般情况下的性能是非常高的,自己实现的内存池不能保证bugfree并且性能也不一定比内置的malloc强,leveldb的arena实现仅供学习和参考。我猜想由于leveldb需要特殊的字对齐内存分配功能,所以作者用arena实现了定制的内存池。
To be continued
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。