源码:
https://github.com/google/leveldb/blob/master/util/arena.h
https://github.com/google/leveldb/blob/master/util/arena.cc参考:
http://mingxinglai.com/cn/2013/01/leveldb-arena/
http://blog.itpub.net/26239116/viewspace-1832774/
简介
Arena是LevelDB中的内存池,SkipList就用到了Arena来分配新节点所需的空间,然后在此空间上调用节点的构造函数来进行初始化:
char* mem = arena_->AllocateAligned(
sizeof(Node) + sizeof(port::AtomicPointer) * (height - 1));
return new (mem) Node(key);
在arena对象被销毁时,析构函数会将内存是释放掉。
成员变量
private:
// Allocation state
char* alloc_ptr_; // 指向空闲空间的指针
size_t alloc_bytes_remaining_; // 剩余可用空间大小
// Array of new[] allocated memory blocks
std::vector<char*> blocks_; // 申请的每块空间都放到vector中
// Total memory usage of the arena.
port::AtomicPointer memory_usage_; // 统计内存使用量
此外,每块空间的大小为4KB
static const int kBlockSize = 4096;
总结一下:
arena管理了若干块空间,每块大小为4KB,每块空间的首地址存放在vector中;
alloc_ptr_
存放的是当前可以用于分配空间的块的空余空间首地址(这句话说的好绕)
alloc_bytes_remaining_
指示alloc_ptr_
后面有多少空间可用
构造函数/析构函数
Arena::Arena() : memory_usage_(0) {
alloc_ptr_ = NULL; // First allocation will allocate a block
alloc_bytes_remaining_ = 0;
}
Arena::~Arena() {
for (size_t i = 0; i < blocks_.size(); i++) {
delete[] blocks_[i];
}
}
构造函数执行了一些初始化,析构函数将vector中管理的内存空间释放。
Public 成员函数
暴露给用户的成员函数有三个:
// 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());
}
首先分析Allocate
:
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); // 不允许申请大小为0的空间
if (bytes <= alloc_bytes_remaining_) { // 判断当前可用空间是否够用
char* result = alloc_ptr_; // 够用的话就直接从可用空间中分贝所需内存
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
return AllocateFallback(bytes); // 当前可用空间不足,调用AllocateFallback
}
逻辑比较简单 ,如果当前块够用,就从当前块分配;否则调用AllocateFallback
那么AllocateFallback做了些什么呢?
char* Arena::AllocateFallback(size_t bytes) {
if (bytes > kBlockSize / 4) { // 申请空间大小大于块大小的四分之一(这里为1KB),则直接申请相应大小的块返回。
// 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;
}
如果在申请一块大内存(大于1KB)时,“池”中可用空间不足了,那么算法将新申请等量一块空间返回,“池”中的剩余空间(肯定是<=1KB)继续留给小内存申请使用;
如果申请一块小内存时,“池”中可用空间不足了,那么就申请一块空间,在这块空间上进行空间分配。这是原“池”中的剩余空间就被浪费了。
这种分配策略照顾了对于小内存的分配(这里小内存为大小小于1KB的内存)。因为当申请内存的请求中,既有大内存申请,又有小内存申请时,“池”中剩余的1KB总是留给小内存申请使用,减少了申请小内存时的申请开销。
其中AllocateNewBlock
函数用于申请一块空间,然后将该空间首地址放入vector中,并更新内存使用量,最后返回该块空间的首地址。
char* Arena::AllocateNewBlock(size_t block_bytes) {
char* result = new char[block_bytes];
blocks_.push_back(result);
memory_usage_.NoBarrier_Store(
reinterpret_cast<void*>(MemoryUsage() + block_bytes + sizeof(char*)));
return result;
}
AllocateAligned函数用于分配内存对齐的空间。(不懂内存对齐? 简单的说,变量的地址为机器字长的倍数时,读取该变量会更快)
char* Arena::AllocateAligned(size_t bytes) {
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 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_) {
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;
}
通常指针的大小与机器字长相同。所以该函数通过计算指针占用空间得到对齐策略align,那么分配的空间地址只要是align的倍数就达到了内存对其的目的。(但是,貌似对于32位机器,也是按照8字节对其的)
使用reinterpret_cast<uintptr_t>(alloc_ptr_)
将指针转换为一个unsigned int
(reinterpret_cast这个功能要记住)。接着计算对于align的余数 current_mod, 计算方法& mask
,而没用%
。(高手!)
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
然后可以确定当前指针需要向前移动多少才能按照align对其:
slop = (current_mod == 0 ? 0 : align - current_mod);
那么只需让指针加上slop就可以得到一个内存对其的地址啦!