LevelDB 源码阅读之 Arena(内存管理器)

简介

Arena 是一个简单的内存分配器。通过一个 Arena 对象,可以进行一些内存分配操作。Arena 对象会将所有分配的内存都记录在案,在 Arena 对象析构的时候一起释放所有记录在案的已分配内存。

Arena 默认分配的内存大小是 4096KB,也就是 4K。每当有一个内存分配请求分配 X Bytes 的内存的时候,Arena 的分配原则如下:

1、如果当前分配的内存块中剩余的内存能够满足该分配请求,也就是说大于等于 X,那么就在当前的内存块中分配 X Bytes 给用户,否则:
2、如果 X 大于默认的内存块大小的 25%,也就是说 X > 1KB,那么就单独分配大小恰好是 X Bytes 的内存给用户,否则:
3、新分配一块大小为 4KB 的内存,在里面分配 X Bytes 给用户(也就是说,上一块内存中可能有些内存没有被分配给用户就被 Arena 抛弃了,这里会造成小于 1KB 的内存浪费)

所以,这里有两个数字我很疑惑,一个就是默认内存块的大小 4KB,另一个就是是否需要单独分配内存块的判断标准:1/4=25%,这两个数字我觉得才是整个内存分配机制中的核心,然而目前为止我还没有搞明���。有人告诉我 4KB 是内核中默认一个 page 的大小,这一点我还不是特别确定。至于 25% 估计是一个经验值,想要内存分配器的性能高,就要尽量减少内存碎片(堆上的碎片)和内存分配的次数,权衡各方利弊之后,25% 估计就是这样一个经验值

此外,Arena 还提供了分配地址对齐的内存块的接口。什么意思呢?调用这个接口,用户会得到他想要的 X Bytes 的内存块,而这个内存块的起始地址是 8 的倍数。我们知道,在 32 位机器上,地址总线是 32 bits 的,也就是说,指针的 size 是 4 Bytes,而 64 位机器上,地址总线是 64 bits 的,指针的 size 是 8 Bytes 的。CPU 取内存中的数据并不是一个 Byte 一个 Byte 的取,在 32 位机器上,一次取的数量是 4 Bytes,64 位机器上一次取 8 Bytes。

扯远了,Arena 的这个分配地址对齐的内存块的接口,其返回的内存块的起始地址是 8 的整数倍。也就是说,返回的地址是这两种形式:"0x_______0" 和 "0x_______8"。要是在分配内存的时候发现当前的地址并不是这种形式,那就多分配点内存用来填补不够的 Byte,最多可能浪费 7 个 Byte。接下来的分配原则就和上面讲的三大原则一致了。

值得一提的是,每次我们从堆中 new 一块内存的时候,操作系统返回的该内存块的首地址是一定能被 8 整除(地址对齐)的。

关于内存分配部分,这里都讲的差不多了,代码我就贴一点点吧。

成员变量

// Allocation state
char* alloc_ptr_;                  // 指向当前内存块中剩余未分配内存块的首部
size_t alloc_bytes_remaining_;     // 保存当前内存块中剩余的可分配出去的内存大小

// Array of new[] allocated memory blocks
std::vector<char*> blocks_;        // 保存所有分配的内存块

// Bytes of memory in blocks allocated so far
size_t blocks_memory_;             // 保存该 Arena 对象总共申请内存的大小

对外接口

Arena();
~Arena();
char* Allocate(size_t bytes);
char* AllocateAligned(size_t bytes);
size_t MemoryUsage() const {
  return blocks_memory_ + blocks_.capacity() * sizeof(char*);
}

其中,Allocate(size_t) 用来分配非地址对齐的内存,AllocateAligned(size_t) 用来分配地址对齐的内存

值得一提的是,Arena 对象不允许拷贝操作,为了完成这个,它把以一个 Arena 对象作为参数的构造函数声明称私有的,并重载 '=' 为私有

关于分配内存的三条原则,可以在 Allocate(size_t) 的实现和一个私有成员函数 char* AllocateFallback(size_t bytes); 中查看:

static const int kBlockSize = 4096;

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

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;
}

char* Arena::AllocateNewBlock(size_t block_bytes) {
  char* result = new char[block_bytes];
  blocks_memory_ += block_bytes;
  blocks_.push_back(result);
  return result;
}

代码中的注释已经很好的解释其行为了,我就不多嘴了。

你可能感兴趣的:(LevelDB 源码阅读之 Arena(内存管理器))