LevelDB : Arena

源码:
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就可以得到一个内存对其的地址啦!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值