Leveldb Arena解析
RT
简介
Arena 是leveldb 实现的简单的内存池,以最小4096bytes 为单位申请block, 使用指针记录当前block 中空余内存起始位置以及当前block剩余空间。将所有的block 放到blocks_ 数组中。Arena 提供了分配内存以及分配对齐的内存的两种接口,没有释放内存的接口,当Arena 的生命周期结束时,由Arena 的析构函数统一释放内存。Arena 的主要结构如下图所示:
Leveldb 代码实现
leveldb arena 实现在util/arena.h 以及 util/arena.cc
接口以及成员
class Arena {
public:
Arena();
~Arena();
// Return a pointer to a newly allocated memory block of "bytes" bytes.
// 分配指定大小的内存,并返回分配内存的首地址
char* Allocate(size_t bytes);
// 分配指定大小并且对齐的内存
// Allocate memory with the normal alignment guarantees provided by malloc
char* AllocateAligned(size_t bytes);
// Returns an estimate of the total memory usage of data allocated
// by the arena.
// 统计使用了多少内存
size_t MemoryUsage() const {
return reinterpret_cast<uintptr_t>(memory_usage_.NoBarrier_Load());
}
private:
// 分配内存,根据情况决定是否使用新的block
char* AllocateFallback(size_t bytes);
// 分配一个新的block
char* AllocateNewBlock(size_t block_bytes);
// Allocation state
// 指向一个block中未被使用的内存首地址
char* alloc_ptr_;
// 表示一个block还剩余多少空间
size_t alloc_bytes_remaining_;
// Array of new[] allocated memory blocks
// 内存池数组
std::vector<char*> blocks_;
// Total memory usage of the arena.
// 统计内存使用,原子变量
port::AtomicPointer memory_usage_;
// No copying allowed
// 将拷贝构造和赋值构造设置为私有
Arena(const Arena&);
void operator=(const Arena&);
};
分配指定bytes 的内存
首先根据alloc_bytes_remaining_判断是否需要分配一个新的block ,如果不需要的话,则重新设置alloc_ptr_ 以及 alloc_bytes_remaining_,否则调用AllocFallback方法分配新的block,并且判断新的block 是否可以被复用。
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);
// 判断是否有可被复用的block
if (bytes <= alloc_bytes_remaining_) {
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
return AllocateFallback(bytes);
}
分配指定bytes 对齐的内存
与上一个接口不同的地方在于,将传入的bytes 化为可对齐内存的大小
char* Arena::AllocateAligned(size_t bytes) {
// 获取当前系统指针大小
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
// 指针大小必须是2的整数次幂,2的整数次幂的二进制表示中
// 有且只有1位是1
assert((align & (align-1)) == 0); // Pointer size should be a power of 2
// 判断bytes是不是align 的整数倍,由于align是2的
// 整数次幂,所以对align的取模运算可以转化为
// 对(align - 1)进行按位与
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
// 为了对齐内存需要新增的大小
size_t slop = (current_mod == 0 ? 0 : align - current_mod);
// needed 表示分配对齐的内存所需的大小
// 后面的逻辑同前
size_t needed = bytes + slop;
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;
}
AllocateFallback
函数的作用是分配一个新的block。不同的是,如果bytes 小于blocksize 的四分之一,则此新分配的block 可以被继续复用。否则的话直接分配新的block, Arena 中可被复用的block 保持不变。
char* Arena::AllocateFallback(size_t bytes) {
// 分配的bytes较小,此新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.
// 此新分配的block可以被复用,重新设置
// 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
直接使用new分配符分配指定大小的内存,并且更新memory_usage_
char* Arena::AllocateNewBlock(size_t block_bytes) {
// 分配空间
char* result = new char[block_bytes];
// 新的block 加入内存池
blocks_.push_back(result);
// 更新memory_usage_
memory_usage_.NoBarrier_Store(
reinterpret_cast<void*>(MemoryUsage() + block_bytes + sizeof(char*)));
return result;
}
析构
逐个释放内存池中的block
Arena::~Arena() {
for (size_t i = 0; i < blocks_.size(); i++) {
delete[] blocks_[i];
}
}
总结
Leveldb Arena 实现简单,Arena 的设计与leveldb 特定的应用场景相关,比如一个memtable 使用一个Arena对象统一获取内存,当memtable对象声明周期结束时,由Arena 统一释放内存,不需要消费者每次new一片内存就要自己delete 掉。