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


      内存管理对于任何程序都是很重要的一块,leveldb自己也实现了一个简单了内存分配器,而不是使用一些其他开源软件tcmalloc等,避免了对其他软件的依赖。

    自己实现内存分配器有什么好处呢? 我认为主要有以下几点:

    1. 内存池的主要作用是减少new  、 delete 等的调用次数,也就是减少系统调用的开销。

    2. 减少内存碎片。

    3. 方便内存使用统计和监控。

    Arena 按块申请内存,每块大小为4k(这个值应该是和分页大小相关),然后使用vector保存这些块的首地址,模型如下:

    成员变量与publlic调用接口:

    class 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对象生命的终结而被回收, 因此使用时需要保证所申请的内存不再使用了,然后才能析构Arena对象。

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

    此外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、...... 字节大小做了多级管理,当前级不能再使用的内存还可以供下一级使用,基本很少有内存浪费,不过也因此带来了维护这个结构更高的复杂度,也需要额外保存更多的冗余信息。

  • 相关阅读:
    .Net 控制台动态刷新使用
    VS 将!=转换成 ≠
    C# 自定义Html分页
    .NET MVC ModelBinder基本使用
    C# 重启电脑 程序自启动
    ASP.NET JsonResult返回日期格式及首字母大写解决
    java快速排序代码实现
    系统高可靠的设计需要考虑哪些方面
    sentinel源码学习--transport模块
    TiDB学习笔记分享--存储篇
  • 原文地址:https://www.cnblogs.com/leogn/p/7602570.html
Copyright © 2020-2023  润新知