【LevelDB】【utils】Arena类解析

发布时间 2023-12-13 21:56:59作者: 静塘花开

Arena类

  Arena类是极为简易的内存池实现,通过RAII机制保证Arena对象管理的内存在Arena对象生命周期结束后自动清理

  源文件位置

    util/arena.h

    util/arena.cc

公共接口

char* Allocate(size_t bytes); // 从Arena中申请指定大小的内存块
char* AllocateAligned(size_t bytes); // 从Arena中申请指定大小的内存块(内存对齐)
size_t MemoryUsage() const // 获取内存使用率;

私有接口

char* AllocateFallback(size_t bytes); // 在Arena剩余的已申请的内存空间不足时用于申请新的内存块

char* AllocateNewBlock(size_t block_bytes); // 调用new申请block_bytes大小的内存并更新blocks_及memory_usage_

私有成员变量

char* alloc_ptr_; // 当前block内空闲内存的起始地址
size_t alloc_bytes_remaining_; // 当前block内空闲内存大小
std::vector<char*> blocks_; // 申请的所有内存块,由vecotr进行管理
std::atomic<size_t> memory_usage_; // Arena内存使用率

构造函数/析构函数

Arena(); // 初始化各成员变量,初始的Arena是空的,并不像常见的内存池实现中会预申请指定大小的内存块
~Arena(); // 释放所有申请的内存块

关键接口

char* AllocateFallback(size_t bytes);

实现思想:

  申请的内存超出4096字节时,直接分配新的block给使用者,不改变alloc_ptr_及alloc_bytes_remaining指向,避免浪费内存

  申请的内存低于1024字节时,将申请新的4096字节大小的block,此时将会放弃原block中剩余的内存(因为alloc_ptr以及alloc_bytes_remaining将会指向新的block)

源码(注释版):

char* Arena::AllocateFallback(size_t bytes) {
  // 若bytes大于1024字节时,恰好申请bytes字节大小的block并将此block作为结果返回
  // 此时Arena的alloc_ptr_及alloc_bytes_remaining_仍指向的具有剩余空间的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;
  }

  // 直接申请4096字节大小的block,此时将会放弃原block中剩余的内存(因为alloc_ptr_以及alloc_bytes_remaining_将会指向新的block)
  // We waste the remaining space in the current block.
  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

  // 从新的block中分配bytes大小的空间
  char* result = alloc_ptr_;
  alloc_ptr_ += bytes;
  alloc_bytes_remaining_ -= bytes;
  return result;
}
char* AllocateAligned(size_t bytes);
实现思想:
  以内存对齐的方式申请内存,除去计算内存对齐需补齐的slop外逻辑与Allocate等同
源码(注释版):
char* Arena::AllocateAligned(size_t bytes) {
  // 获取环境上应以多少位进行字节对齐(最低为8字节对齐)
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
  // 指针大小必须为2的幂,以8字节对齐为例(即align为8),1000 & 0111结果为0
  // 若align非2的幂值,由于align、align - 1的最高位非0的最高位必然相同,因此两者结果必定不为0,(以align为9为例),1001 & 1000结果为8
  static_assert((align & (align - 1)) == 0,
                "Pointer size should be a power of 2");
  // 计算当前alloc_ptr_指向的地址若按align对齐,有多少字节是不满足对齐要求的,类似reinterpret_cast<uintptr_t>(alloc_ptr_) % align
  // 因此为了字节对齐,需要额外补充align - current_mod字节
  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;
}