为了高效回收和利用内存空间而创建的类

class Arena {  
 public:  
  Arena();  
  
  Arena(const Arena&) = delete;  
  Arena& operator=(const Arena&) = delete;  
  
  ~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 memory_usage_.load(std::memory_order_relaxed);  
  }  
  
 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.  
  //  // 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_;  
};
  • blocks_中的每个元素都是使用new[]申请的内存块
  • memory_usages_使用原子类型记录的内存使用量,可以不用加锁。
  • alloc_ptr_alloc_bytes_remaining_:则是用于内存分配的状态,来看一下下面的内存分配代码就有比较清晰的认识了
inline char* Arena::Allocate(size_t bytes) {  
  // 为了避免混乱,不允许申请的bytes=0.否则可能有多个指向同一地址的指针
  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也可以看到,申请新的内存空间的时候也不是原原本本按照给定的大小进行申请,而是根据申请的大小进行不同的处理

static const int kBlockSize = 4096;
char* Arena::AllocateFallback(size_t bytes) {  
  // 当申请的字节大小超过kBlockSize的四分之一的时候,按照给定的大小分配,这样来避免空间的浪费。
  if (bytes > kBlockSize / 4) {   
    return result;  
  }  
  // 申请的字节比较少的情况时候,直接申请4k大小的内存块。这个块可以被分配成很多小内存,以提高内存的使用率以及减少内存分配的次数,提高效率
  alloc_ptr_ = AllocateNewBlock(kBlockSize);  
  alloc_bytes_remaining_ = kBlockSize;  
  
  char* result = alloc_ptr_;  
  alloc_ptr_ += bytes;  
  alloc_bytes_remaining_ -= bytes;  
  return result;  
}

至于实际执行内存分配的AllocateNewBlock,也就是调用new关键字进行分配

char* Arena::AllocateNewBlock(size_t block_bytes) {  
  char* result = new char[block_bytes];  
  blocks_.push_back(result);
  // 把指针的大小也统计到内存使用量上了
  memory_usage_.fetch_add(block_bytes + sizeof(char*),  
                          std::memory_order_relaxed);  
  return result;  
}

Arena将所有申请的内存块都保存在了blocks_数组当中,在Arena对象释放的时候再统一释放掉这个内存

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

不要遗漏了还有AllocateAligned这个函数,主要是为了保证申请的内存块是内存对齐的,新分配的块自然不需要考虑,都是内存对齐的。但是将申请的大块拆分为小块的时候就需要注意了内存对齐了。

char* Arena::AllocateAligned(size_t bytes) {  
  // 申请的指针需要按照align对齐  
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;  
  static_assert((align & (align - 1)) == 0,  
                "Pointer size should be a power of 2");  
  // 对align取模的位运算  
  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 申请新内存,返回的总是内存对齐的地址  
    result = AllocateFallback(bytes);  
  }  
  assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);  
  return result;  
}