当前位置:Gxlcms > 数据库问题 > leveldb 阅读笔记(1) 内存分配

leveldb 阅读笔记(1) 内存分配

时间:2021-07-01 10:21:17 帮助过:27人阅读

Arena { public: // 返回分配好的内存块 char* Allocate(size_t bytes); // 返回分配好的内存块,首地址满足字节对齐 char* AllocateAligned(size_t bytes); // 已使用内存的估算大小(因为使用了stl的vector,精确大小不好确定) size_t MemoryUsage() const { return reinterpret_cast<uintptr_t>(memory_usage_.NoBarrier_Load()); } private: // Allocation state char* alloc_ptr_;                //指向当前4k块使用进度 size_t alloc_bytes_remaining_;        //当前4k块剩余大小 // Array of new[] allocated memory blocks std::vector<char*> blocks_;          //记录所申请各块内存的首地址,方便回收 // Total memory usage of the arena. port::AtomicPointer memory_usage_;      //已经使用内存大小的估算 }

内存分配策略:

1. 当需剩余的内存大小满足分配需求时,直接使用剩余的内存(之前一次性申请了一大块,还有些没用完)

   否则需要向系统重新申请一块。

2. 当前块剩余的内存大小不满足分配需求,并且需要分配的内存比较大时(>4096/4 = 1k),单独申请一块独立的内存。

3. 当前块剩余的内存不够并且新的分配需求不大于1k时, 另外申请一大块4k,从中取出部分返回给调用者,余下的供下次使用。

 

源码的注释中也说到了,上面第2点是为了避免过多的内存浪费,为什么这么做就能避免呢?  考虑一种情况:

假如当前块还剩余1k大小,分配需求是 1025 bytes > 1k, 不按上面的做法的话,就需要新申请一个4k块从中取出1025 bytes返回,然而这么做的话,上一块剩余的1k就再也不会被使用了,这就是浪费。 反而之前剩余的1k内存还可以继续使用。

因此这种做法避免了大块的浪费,然而仍有可能浪费1k之内的内存,为什么不把这个值设的很小呢?   那就和直接使用new差不多了,失去了内存分配器的原有意义,设置成这个值是一个权衡利弊的结果。

具体实现:

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_.push_back(result);
  memory_usage_.NoBarrier_Store(
      reinterpret_cast<void*>(MemoryUsage() + block_bytes + sizeof(char*)));
  return result;
}

 

此外Arena 还提供了一个保证直接对齐的方法:

char* Arena::AllocateAligned(size_t bytes) {
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;  // 按一个指针所占大小的字节对其,最少为8
  assert((align & (align-1)) == 0);                  // 确保对其大小时2的幂
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);   // 相当于对align取模
  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; }

 

总结:

leveldb实现的内存分配器还是很简单的,有点简陋的感觉。相对于leveldb只用一个vector维护,c++ stl所实现的默认的内存分配器就精细多了,它按8、16、32、...... 字节大小做了多级管理,当前级不能再使用的内存还可以供下一级使用,基本很少有内存浪费,不过也因此带来了维护这个结构更高的复杂度,也需要额外保存更多的冗余信息。

leveldb 阅读笔记(1) 内存分配

标签:方便   分配器   eal   blog   cas   成员变量   current   return   size_t   

人气教程排行