引言
为了更加合理地使用内存,在leveldb中实现了自定义的内存池。
1 数据结构
leveldb的内存池实现类为Arena,其核心数据域如下:
首先,用一个vector类型的blocks_存储分配的一块块内存;用alloc_ptr_指向最新申请的内存中未使用内存的首地址;用alloc_bytes_remaining_表示最新申请的内存中未使用内存的字节大小;特别地,memory_usage_统计了所有的bock和blocks_变量占用的内存和。
2 源码解读
2.1 源码解读
申请内存的方法有Allocate和AllocateAligned,后者多了起始地址对齐功能。
- AllocateAligned函数
char* Arena::AllocateAligned(size_t bytes) {
//进行最低8字节的对齐
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
static_assert((align & (align - 1)) == 0,
"Pointer size should be a power of 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;
}
- AllocateFallback函数
char* Arena::AllocateFallback(size_t bytes) {
if (bytes > kBlockSize / 4) {
// 如果申请内存大小大于1K,那么进入下面,申请bytes大小然后直接使用
char* result = AllocateNewBlock(bytes);
return result;
}
// 如果申请内存大小大于1K,直接申请4K大内存后,还需要重置alloc_ptr_和alloc_bytes_remaining_
alloc_ptr_ = AllocateNewBlock(kBlockSize);
alloc_bytes_remaining_ = kBlockSize;
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
- AllocateNewBlock函数
char* Arena::AllocateNewBlock(size_t block_bytes) {
char* result = new char[block_bytes];
blocks_.push_back(result);
// 加sizeof(char*)是因为blocks_还占用一个指针大小的存储空间
memory_usage_.fetch_add(block_bytes + sizeof(char*),
std::memory_order_relaxed);
return result;
}
2.2 发现问题
通过阅读Arena源码,在如下场景中会造成内存浪费:
- 首先,调用AllocateAligned申请512Byte内存,这时会申请一个4K的大内存,余下3.5K的内存可分配;
- 然后,连续调用AllocateAligned申请512Byte内存6次,最后余下512Byte的内存可分配;
- 继续申请600Byte内存,这时会新new一块4K的大内存,这时alloc_ptr_和alloc_bytes_remaining_记录这4K新的大内存的空余空间。之前4K中余下的512Byte将被浪费。