【LevelDB源码阅读】Arena

是什么

内存分配管理器,主要为skiplist即Memtable服务而不是整个项目。申请内存时,将申请到的内存直接放入vector中,在Arena的生命周期结束后,统一释放掉所有申请的内存,内部结构如下图:

【LevelDB源码阅读】Arena

为什么要用

  • 避免内存碎片,skiplist里面记录的都是用户传进来的key/value,这些字符串有长有短,放到内存中很容易导致内存碎片

学到什么

  • 与(&)操作进行内存对齐
  • atomic保证原子性
  • 使用delete避免拷贝和赋值操作

源码分析

首先看一下内部成员

  // Allocation state
  char *alloc_ptr_;  // 内存偏移量指针,指向未使用内存的首地址
  size_t alloc_bytes_remaining_;  // 剩下内存数

  // Array of new[] allocated memory blocks
  std::vector<char*> blocks_;  // 存储每次分配的内存指针

  // Total memory usage of the arena.
  //
  // TODO(costan): This member is accessed via atomics, but the others are
  //               accessed without any locking. Is this OK?
  std::atomic<size_t> memory_usage_;

构造函数和析构函数

Arena::Arena()  // vector调用默认构造函数初始化
    : alloc_ptr_(nullptr), alloc_bytes_remaining_(0), memory_usage_(0) {}

Arena::~Arena() {
  for (size_t i = 0; i < blocks_.size(); i++) {
    delete[] blocks_[i];
  }
}

拷贝构造和拷贝赋值

  Arena(const Arena&) = delete;
  Arena& operator=(const Arena&) = delete;

主要对外接口

  // 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);

具体实现如下:

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);
  if (bytes <= alloc_bytes_remaining_) {  // 需要内存小于剩余内存,直接分配
    char *result = alloc_ptr_;  // 保存指针,用于返回
    alloc_ptr_ += bytes;
    alloc_bytes_remaining_ -= bytes;
    return result;
  }
  return AllocateFallback(bytes); // 需要内存大于剩余内存
}

需要内存大于剩余内存时,调用AllocateFallback()分配内存:

  • 如果需要内存大于4096/4,则直接分配一块大小为bytes内存,避免每次剩余内存不可用,造成浪费。
  • 否则,重新分配一个内存块(默认大小4096)用于存储数据,虽然浪费了当前内存块的剩余内存,但当下次再分配小内存时,可以直接使用,减少内存分配次数。

具体实现如下:

char* Arena::AllocateFallback(size_t bytes) {
  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.
  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

  char *result = alloc_ptr_;
  alloc_ptr_ += bytes;
  alloc_bytes_remaining_ -= bytes;
  return result;
}

其中分配新内存块实现如下:
memory_order_relaxed:针对只要求原子操作,除此之外不需要其它同步保证,计数器是一种典型应用场景。

char* Arena::AllocateNewBlock(size_t block_bytes) {
  char *result = new char[block_bytes];
  blocks_.push_back(result);  // 申请块放入vector,以便析构函数释放内存
  memory_usage_.fetch_add(block_bytes + sizeof(char*),
                          std::memory_order_relaxed);
  return result;
}

对齐内存分配

char* Arena::AllocateAligned(size_t bytes) {
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;  // 与系统相关,4或8
  static_assert((align & (align - 1)) == 0,
                "Pointer size should be a power of 2");  // 确保是2的指数次方
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);  // 当前指针模对齐值
  size_t slop = (current_mod == 0 ? 0 : align - current_mod);  // 还差slop个字节对齐
  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;
}

读取内存使用

  // Returns an estimate of the total memory usage of data allocated
  // by the arena.
  size_t MemoryUsage() const {
    return memory_usage_.load(std::memory_order_relaxed);
  }
上一篇:在GDK8下观察glibc堆


下一篇:error: redefinition of 'AllocateAligned'