Leveldb学习之路1----内存管理

源代码路径

util/arena.h
util/arena.cc

Arena简介

数据库对内存的分配释放要求非常严格,频繁的调用malloc/free或者new/delete开销非常大,他们频繁的向操作系统申请堆内存,这个过程涉及到用户态向内核态的切换,在内核态分配完空间后再切换到用户态。同时频繁调用malloc再free容易造成大量的内部碎片。因此我们需要自己构造一个缓冲区域:内存池。我们可以一次性申请一个较大的内存区域,后续的小的内存分配由用户态自己管理。Arena就是Leveldb根据自身特性实现的一个超简易版内存池。他的大致结构如下:
在这里插入图片描述

其中kBlockSize设置为4k。即一般情况下向操作系统申请内存时申请的大小为4k。Arena把每次申请的block都保存在blocks_数组里。
在用户向Arena申请内存流程如下:

  1. Arena会首先判断当前的block中剩余的空间是否满足,如果满足则直接进行分配。否则到2
  2. 为了减少内部碎片(Internal Fragmentation, IF)当申请的内存大于1k时,Arena会直接申请一个新的block供用户使用(如图中的blocks_中的3,4),当前block可以复用.否则转到3
  3. 如果小于1k,则当前block中的空闲内存会被当成内部碎片,Arena会申请一个新的block并切换在新的block中为后续的用户申请分配内存。如图中的blocks_ 0中的黄色即内部碎片。

源代码分析 Internal Fragmentation

数据结构

class Arena {
 private:
  char* AllocateFallback(size_t bytes);
  char* AllocateNewBlock(size_t block_bytes);

  // 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.
  std::atomic<size_t> memory_usage_;
}

alloc_ptr 表示当前block中空闲内存的起始位置
alloc_bytes_remaining_ 表示当前block空闲内存大小
blocks_ 表示内存管理中已分配的block数组
memory_usage_ 表示总共使用的内存大小

内部方法

  1. AllocateNewBlock 的作用是直接申请一个新的block,且新的block大小等于用户所要申请的内存大小。
char* Arena::AllocateNewBlock(size_t block_bytes) {
  // 申请block_bytes大小的内存
  char* result = new char[block_bytes];
  // 将当前block放入blocks中
  blocks_.push_back(result);
  // 更新内存使用情况
  memory_usage_.fetch_add(block_bytes + sizeof(char*),
                          std::memory_order_relaxed);
  return result;
}
  1. AllocateFallback 用在当前block剩余空间不足时申请新的block并进行内存分配。如果要申请的空间过大,这里默认为一页的1/4,则直接分配一个新的block,且当前的block可以继续供后续分配使用。
static const int kBlockSize = 4096;
char* Arena::AllocateFallback(size_t bytes) {
  // 如果申请的空间大于1/4页,则直接调用AllocateNewBlock申请一块
  // 新的block,且当前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.
  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

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

外部接口

  1. Allocate 用来申请内存。如果当前block空闲空间足够,则直接分配内存,否则调用内部AllocateFallback方法。block的申请由AllocateFallback方法负责。
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);
}
  1. AllocateAligned 申请内存并对齐。
char* Arena::AllocateAligned(size_t bytes) {
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
  // 做过leetcode都知道:align & (align - 1)可以用来判断 align 是不是
  // 2的n次方。
  static_assert((align & (align - 1)) == 0,
                "Pointer size should be a power of 2");
  // (align - 1) 是2^n-1, 二进制全为1。这样求&位运算表示取模。
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
  // 计算需要填充的bit数slop。
  size_t slop = (current_mod == 0 ? 0 : align - current_mod);
  // 计算出经过填充后最终需要申请的内存大小
  size_t needed = bytes + slop;
  // 后续操作类似Allocate
  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;
}
  1. MemoryUsage 查询当前内存使用。
  size_t MemoryUsage() const {
    return memory_usage_.load(std::memory_order_relaxed);
  }

Arena总结

Arena是Leveldb的一个快速内存管理的实现。它只有申请内存操作,没有释放的操作,这和Leveldb的底层数据结构性质息息相关:只有插入操作,没有删除操作(Leveldb在删除key的时候也是以插入新数据的方式删除)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值