源代码路径
util/arena.h
util/arena.cc
Arena简介
数据库对内存的分配释放要求非常严格,频繁的调用malloc/free或者new/delete开销非常大,他们频繁的向操作系统申请堆内存,这个过程涉及到用户态向内核态的切换,在内核态分配完空间后再切换到用户态。同时频繁调用malloc再free容易造成大量的内部碎片。因此我们需要自己构造一个缓冲区域:内存池。我们可以一次性申请一个较大的内存区域,后续的小的内存分配由用户态自己管理。Arena就是Leveldb根据自身特性实现的一个超简易版内存池。他的大致结构如下:
其中kBlockSize设置为4k。即一般情况下向操作系统申请内存时申请的大小为4k。Arena把每次申请的block都保存在blocks_数组里。
在用户向Arena申请内存流程如下:
- Arena会首先判断当前的block中剩余的空间是否满足,如果满足则直接进行分配。否则到2
- 为了减少内部碎片(Internal Fragmentation, IF)当申请的内存大于1k时,Arena会直接申请一个新的block供用户使用(如图中的blocks_中的3,4),当前block可以复用.否则转到3
- 如果小于1k,则当前block中的空闲内存会被当成内部碎片,Arena会申请一个新的block并切换在新的block中为后续的用户申请分配内存。如图中的blocks_ 0中的黄色即内部碎片。
源代码分析 Internal Fragmentation
数据结构
class Arena {
private:
char* AllocateFallback(size_t bytes);
char* AllocateNewBlock(size_t block_bytes);
// Allocation state
char* alloc_ptr_;
size_t alloc_bytes_remaining_;
// Array of new[] allocated memory blocks
std::vector<char*> blocks_;
// Total memory usage of the arena.
std::atomic<size_t> memory_usage_;
}
alloc_ptr 表示当前block中空闲内存的起始位置
alloc_bytes_remaining_ 表示当前block空闲内存大小
blocks_ 表示内存管理中已分配的block数组
memory_usage_ 表示总共使用的内存大小
内部方法
- AllocateNewBlock 的作用是直接申请一个新的block,且新的block大小等于用户所要申请的内存大小。
char* Arena::AllocateNewBlock(size_t block_bytes) {
// 申请block_bytes大小的内存
char* result = new char[block_bytes];
// 将当前block放入blocks中
blocks_.push_back(result);
// 更新内存使用情况
memory_usage_.fetch_add(block_bytes + sizeof(char*),
std::memory_order_relaxed);
return result;
}
- AllocateFallback 用在当前block剩余空间不足时申请新的block并进行内存分配。如果要申请的空间过大,这里默认为一页的1/4,则直接分配一个新的block,且当前的block可以继续供后续分配使用。
static const int kBlockSize = 4096;
char* Arena::AllocateFallback(size_t bytes) {
// 如果申请的空间大于1/4页,则直接调用AllocateNewBlock申请一块
// 新的block,且当前block的剩余空间可在后续使用,减少内部碎片。
if (bytes > kBlockSize / 4) {
// Object is more than a quarter of our block size. Allocate it separately
// to avoid wasting too much space in leftover bytes.
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;
}
外部接口
- Allocate 用来申请内存。如果当前block空闲空间足够,则直接分配内存,否则调用内部AllocateFallback方法。block的申请由AllocateFallback方法负责。
inline char* Arena::Allocate(size_t bytes) {
// The semantics of what to return are a bit messy if we allow
// 0-byte allocations, so we disallow them here (we don't need
// them for our internal use).
assert(bytes > 0);
if (bytes <= alloc_bytes_remaining_) {
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
return AllocateFallback(bytes);
}
- AllocateAligned 申请内存并对齐。
char* Arena::AllocateAligned(size_t bytes) {
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
// 做过leetcode都知道:align & (align - 1)可以用来判断 align 是不是
// 2的n次方。
static_assert((align & (align - 1)) == 0,
"Pointer size should be a power of 2");
// (align - 1) 是2^n-1, 二进制全为1。这样求&位运算表示取模。
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
// 计算需要填充的bit数slop。
size_t slop = (current_mod == 0 ? 0 : align - current_mod);
// 计算出经过填充后最终需要申请的内存大小
size_t needed = bytes + slop;
// 后续操作类似Allocate
char* result;
if (needed <= alloc_bytes_remaining_) {
result = alloc_ptr_ + slop;
alloc_ptr_ += needed;
alloc_bytes_remaining_ -= needed;
} else {
// AllocateFallback always returned aligned memory
result = AllocateFallback(bytes);
}
assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);
return result;
}
- MemoryUsage 查询当前内存使用。
size_t MemoryUsage() const {
return memory_usage_.load(std::memory_order_relaxed);
}
Arena总结
Arena是Leveldb的一个快速内存管理的实现。它只有申请内存操作,没有释放的操作,这和Leveldb的底层数据结构性质息息相关:只有插入操作,没有删除操作(Leveldb在删除key的时候也是以插入新数据的方式删除)。