leveldb - Arena

之前一直听说内存池的概念,却不知为什么要用内存池,怎么去写一个内存池。
今天我们就通过leveldb的Arena来学习一下内存池的使用。

部分内容参考
leveldb Arena

背景

我们都知道,对于一个高性能的服务器端程序来说,内存的使用非常重要。C++ 提供了 new/delete 来管理内存的申请和释放,但是对于小对象来说,直接使用 new/delete 代价比较大,要付出额外的空间和时间,性价比不高。
另外,我们也要避免多次的申请和释放引起的内存碎片。一旦碎片到达一定程度,即使剩余内存总量够用,但由于缺乏足够的连续空闲空间,导致内存不够用的假象。
c++ STL为了避免内存碎片,实现一个复杂的内存池,leveldb 中则没有那么复杂,只是实现了一个"一次性"内存池 Arena
在 leveldb 里面,并不是所有的地方都使用了这个内存池,主要是 memtableskiplist 使用,主要是用于临时存放用户的更新数据,由于更新的数据可能很小,所以这里使用内存池就很合适。

原理

为了避免小对象的频繁分配,需要减少对 new 的调用,最简单的做法就是申请大块的内存,多次分给客户。
leveldb 用一 vector 来保存所有的内存分配记录,默认每次申请4k的内存,记录下剩余指针和剩余内存字节数,每当有新的申请,如果当前剩余的字节能满足需要,则直接返回给用户,如果不能,对于超过1k的请求,直接 new 返回,小于1K的请求,则申请一个新的4k块,从中分配一部分给用户。
但是这样的一个问题就是当前块剩余的部分就浪费了,改进的方法,针对每个block都记录剩余字节,这样就需要遍历来查找合适的block,要付出一些性能的代价。google的做法是浪费就浪费吧:-)
至于释放,需要释放整个内存池来释放所占内存,这个和 leveldb 的需求有关, memtable 不需要释放单次内存,flush 到硬盘后整个 memtable 销毁。

草图

leveldb - Arena_第1张图片
Arena.png

相关变量:

        char *alloc_ptr_;                   // 指向当前正在使用的 block 的剩余部分
        size_t alloc_bytes_remaining_;      // 当前使用的 block 剩余字节
        std::vector blocks_;        // 存放 block 的列表
        std::atomic memory_usage_;  // blocks 中所有块占用的内存大小

相关函数:

        // 给定 bytes,返回对应大小的内存空间
        // 若当前 block 空间不够,调用 AllocateFallback()
        char *Allocate(size_t bytes);

        // 分配内存对齐的空间,
        // 同样可能会调用 AllocateFallback()
        char *AllocateAligned(size_t bytes);

        // 返回 blocks_ 中共占用了多少内存空间
        size_t MemoryUsage() const { return memory_usage_.load(std::memory_order_relaxed); }
        
        // 当上一个 block 空间不够时,调用该函数
        // 1. 如果需要的空间大小大于 4096/4 bytes,直接分配一块 bytes 大小的空间然后返回;
        // 2. 如果需要的空间较小,则分配一块 4096 的块,然后一点点地分配给调用对象。
        // 所以第2种情况可能调用 AllocateNewBlock() 函数
        char *AllocateFallback(size_t bytes);

        // 分配一个 4096 大小的块,并加入到 blocks_
        char *AllocateNewBlock(size_t block_bytes);

相关用法

leveldb 中共两处使用到了 Arena, 一个是
memtable 中:Arena arena_
另一处是
skiplist 中:Arena *const arena_

生命周期和 memtableskiplist对象绑定。这两个类的对象销毁时,Arena对象和空间随之销毁。

你可能感兴趣的:(leveldb - Arena)