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

内存管理对于任何程序都是很重要的一块,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 还提供了一个保证直接对齐的方法:

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

时间: 2024-08-10 21:29:42

leveldb 阅读笔记(1) 内存分配的相关文章

深入理解Java虚拟机之读书笔记三 内存分配策略

一般的内存分配是指堆上的分配,但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配.对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况下直接分配在老年代中,分配的规则并不是百分之百固定的.细节取决于哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置. 一.对象优先在Eden分配 优先在新生代的Eden区中分配,当不够时,发起一次Minor GC. 二.大对象直接进入老年代 需要连续大量内存空间的Java对象,长字符串和数组等. 三.

C语言学习笔记--动态内存分配

1. 动态内存分配的意义 (1)C 语言中的一切操作都是基于内存的. (2)变量和数组都是内存的别名. ①内存分配由编译器在编译期间决定 ②定义数组的时候必须指定数组长度 ③数组长度是在编译期就必须确定的 (3)但是程序运行的过程中,可能需要使用一些额外的内存空间 2. malloc 和 free 函数 (1)malloc 和 free 用于执行动态内存分配的释放 (2)malloc 所分配的是一块连续的内存 (3)malloc 以字节为单位,并且返回值不带任何的类型信息:void* mallo

C学习笔记——malloc内存分配

鉴于上次领导告诉一个解决方案,让我把它写成文档,结果自己脑子里知道如何操作和解决,但就是不知道如何用语言文字把它给描述出来.决定以后多写一些笔记和微博来锻炼自己的文字功底和培养逻辑思维,不然只会是一个敲代码的,永远到不了管理的层面. 把<C程序设计语言>细读了一遍后,到第8章UNIX系统接口的最后两节--"目录列表"和"存储分配程序",看了一遍都没看懂.智商不过高啊.把存储分配重新看了一遍,才有了眉头.这两天还要找时间把目录列表再看一遍,确保掌握.(前几

leveldb 阅读笔记 (2) 简易测试框架

随leveldb一起开源的代码中,还包括一些测试程序, 发现这些测试程序都使用了一些公共的部分代码,很容易编写多个测试用例,自动运行,还能生成测试报告.原来这就是一个简单的测试框架啊,非常实用,实现也很美观,因此记下来. 自动化测试中的必不可少的过程,是需要针对不同的输入条件自动执行测试对象程序,比较输出结果和预期答案,并且提供测试报告, 而使用leveldb中的测试框架实现这些是件很方便的事情. 测试过程中的断言: 每使用一个断言都会产生一个 Tester 的临时对象,断言的时候还允许附加额外

深入java虚拟机阅读笔记(jvm内存原理、异常处理部分)

深入理解Java虚拟机:JVM高级特性与最佳实践 阅读笔记(内存原理.异常处理): 1.     Jvm运行时,内存划分如图所示: 2.     程序计数器: Jvm将这个计数看作当前线程执行某条字节码的行数,会根据计数器的值来选取需要执行的操作语句.这个属于线程私有,不可共享,如果共享会导致计数混乱,无法准确的执行当前线程需要执行的语句. 该区域不会出现任何OutOfMemoryError的情况. 3.     虚拟机栈 经常说到的栈内存就是指虚拟机栈.Java中每一个方法从调用直至执行完成的

Linux 2.6 内核阅读笔记 内存管理

2014年7月29日 buddy分配算法 内核需要为分配一组连续的页框提供一种健壮.高效的分配策略.分配连续的页框必须解决内存管理中的外碎片(external fragmentation).频繁的请求和释放不同大小的一组连续页框,必然导致分配页框的块分算来许多小块的空闲页框无法被一次性大量分配使用. linux内核采用著名的伙伴系统算法来解决外碎片问题.该算法的核心思想是把所有的空闲页框分成11个链块表.每个链块表的大小分别为1,2,4,8,16,32,64,128,256,512和1024个连

C++ Primer 学习笔记_98_特殊工具与技术 --优化内存分配

特殊工具与技术 --优化内存分配 引言: C++的内存分配是一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象.new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象. new基于每个对象分配内存的事实可能会对某些类强加不可接受的运行时开销,这样的类可能需要使用用户级的类类型对象分配能够更快一些.这样的类使用的通用策略是,预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象. 另外一些类希望按最小尺寸为自己的数据成员分配需要的内存.例如,

C++ Primer 学习笔记_99_特殊工具与技术 --优化内存分配[续1]

特殊工具与技术 --优化内存分配[续1] 三.operator new函数和operator delete 函数 – 分配但不初始化内存 首先,需要对new和delete表达式怎样工作有更多的理解.当使用new表达式 string *sp = new string("initialized"); 的时候,实际上发生三个步骤: 1)首先,表达式调用名为operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象; 2)接下来,运行该类型的一个构造函数

C++ Primer 学习笔记_100_特殊工具与技术 --优化内存分配[续2]

特殊工具与技术 --优化内存分配[续2] 七.一个内存分配器基类 预先分配一块原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造:释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统.这种策略常被称为维持一个自由列表.可以将自由列表实现为已分配但未构造的对象的链表. 我们将定义一个名为 CachedObj 的新类来处理自由列表.像 QueueItem 这样希望优化其对象分配的类可以使用 CachedObj 类,而不用直接实现自己的 new 和 del