leveldb通过Arena类来进行内存的申请和管理。Arena类只是一个内存管理器,只负责申请内存和管理内存,不能进行内存的二次分配,因此不能将其看做是一个内存池。
class Arena {
public:
Arena();
~Arena(){//生命周期结束后,统一释放所有block
for (size_t i = 0; i < blocks_.size(); i++) {//释放每个block
delete[] blocks_[i];//每个block都是通过new char[]来分配的,因此要使用delete []释放
}
}
char* Allocate(size_t bytes);
char* AllocateAligned(size_t bytes);
size_t MemoryUsage() const {
return blocks_memory_ + blocks_.capacity() * sizeof(char*);
}
private:
char* AllocateFallback(size_t bytes);
char* AllocateNewBlock(size_t block_bytes);
char* alloc_ptr_;//上次内存分配的结束位置
size_t alloc_bytes_remaining_;//当前block中的剩余可用字节数
std::vector<char*> blocks_;//每个通过new []分配的block的起始地址,不同block大小可以不一样
size_t blocks_memory_;//所有分配的block的总大小
// No copying allowed
Arena(const Arena&);
void operator=(const Arena&);
};
Arena类对外只提供了两个内存分配请求的函数和一个统计所用内存大小的函数,内存分配分为直接分配和对齐分配两种。
1、直接分配:
inline char* Arena::Allocate(size_t bytes) {
assert(bytes > 0);
if (bytes <= alloc_bytes_remaining_) {//需要内存小于当前block的剩余大小,则直接在该block中分配
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
return AllocateFallback(bytes);//否则申请一个新的block再进行分配
}
char* Arena::AllocateFallback(size_t bytes) {
//kBlockSize=4K(1个page),当bytes大于1/4页时,按照实际需要内存分配
if (bytes > kBlockSize / 4) {
char* result = AllocateNewBlock(bytes);
return result;
}
// We waste the remaining space in the current block.
alloc_ptr_ = AllocateNewBlock(kBlockSize);//否则分配一个新页
alloc_bytes_remaining_ = kBlockSize;
char* result = alloc_ptr_;//然后从新页的起始位置开始分配
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
2、字节对齐分配
char* Arena::AllocateAligned(size_t bytes) {
//首先要得到进行字节对齐时,实际要分配内存的总大小
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;//32位为4字节,64位8字节
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_) {//直接在当前block中分配
result = alloc_ptr_ + slop;//此次分配的起始位置
alloc_ptr_ += needed;//此次分配的结束位置
alloc_bytes_remaining_ -= needed;
} else {
result = AllocateFallback(bytes);//重新申请一个block,在新的block中分配
}
assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
return result;
}
以4字节对齐为例(align=4):
align-1=3=11,因为是4字节对齐,而从第三位开始都是4的倍数,因此只与最低两位有关
alloc_ptr为上次分配的结束位置, reinterpret_cast是将其转化为一个无符号整数。
我们只关注最低两位:
alloc_ptr: 00 01 10 11 (起始地址最低两位分别为0,1,2,3)
align-1: 11 11 11 11
current_mod: 00 01 10 11
slop: 0 3 2 1 (需要多分配才能实现对齐的字节数)
即当alloc_ptr=0已经对齐时,slop=0,实际需要内存不变
当alloc_ptr=1时,slop=3,还需要多分配3字节才能对齐
当alloc_ptr=2时,slop=2,还需要多分配2字节才能对齐
当alloc_ptr=3时,slop=1,还需要多分配1字节才能对齐
这样通过Arena就实现了两种内存分配方式,Arena分配的结果示意图如下:
Arena只能对这些内存进行申请和管理,不能进行二次分配,且只能等到Arena的生命周期结束后才能将所有block一起释放。
一般一个Memtable对应一个Arena,因此会等Memtable释放后,统一释放Arena