RT

简介

Arena 是leveldb 实现的简单的内存池,以最小4096bytes 为单位申请block, 使用指针记录当前block 中空余内存起始位置以及当前block剩余空间。将所有的block 放到blocks_ 数组中。Arena 提供了分配内存以及分配对齐的内存的两种接口,没有释放内存的接口,当Arena 的生命周期结束时,由Arena 的析构函数统一释放内存。Arena 的主要结构如下图所示:
leveldb 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 掉。