leveldb : Arena内存池

  • Arena
    • Arena内存管理模型
    • Arena的构造与析构实现
    • Arena提供的接口
      • Allocate
      • AllocateFallback
      • AllocateNewBlock
      • AllocateAligned
    • 总结

Arena

  Arenaleveldb 项目里面使用的轻量级的内存池对象leveldb 用这个对象来管理内存的分配,简化了 newdelete 的调用。Arena的代码并不多,但也集成了google工程师巧妙的思维和想法,接下来就慢慢揭开Arena神秘的面纱。

Arena源码链接:

头文件

源码实现

Arena内存管理模型

  先来看看Arena中比较重要的成员变量

//每一个block的大小为4096字节
static const int kBlockSize = 4096;//当前block未分配内存的起始地址,也是已分配内存的结束地址
char* alloc_ptr_;
//当前block剩余的未分配内存大小
size_t alloc_bytes_remaining_;  //管理所有block的vector,类似于STL库中deque源码实现中的map的作用
std::vector<char*> blocks_;
//Arena已经分配的总内存
std::atomic<size_t> memory_usage_;

  了解了Arena的核心成员变量后,我们可以画图说明Arena的内存管理模型以及每个成员变量的意义:

  Arena所做的事为申请内存和分配内存,申请内存由new操作完成,而分配内存主要体现在alloc_ptr_指针后移以及alloc_bytes_remaining_减少的操作上(当然也有其他情况,后文中将提到)。

Arena的构造与析构实现

//构造函数
Arena::Arena(): alloc_ptr_(nullptr), alloc_bytes_remaining_(0), memory_usage_(0) {}//析构函数,释放blocks_中的每块内存
Arena::~Arena() {for (size_t i = 0; i < blocks_.size(); i++) {delete[] blocks_[i];}
}

Arena提供的接口

  Arena提供的三个public接口如下:

// 基本的内存分配函数
char* Allocate(size_t bytes);// 字节对齐分配内存
char* AllocateAligned(size_t bytes);// 返回已分配内存的总大小
size_t MemoryUsage() const {return memory_usage_.load(std::memory_order_relaxed);
}

Allocate

  基本内存分配函数,根据传入的byte值分配相应的内存,分配内存的规则如下:

  1. bytes小于当前块剩余内存大小,则直接分配,并对alloc_ptralloc_bytes_remaining做响应调整
  2. bytes大于当前块剩余内存大小,且小于kBlockSize / 4 = 1024,则申请一个新的block,将alloc_ptr的值移到新的block上,并将alloc_bytes_remaining的值设为kBlockSize - bytes
  3. bytes大于kBlockSize / 4 = 1024,则直接分配一块大小为bytes的内存,alloc_ptralloc_bytes_remaining不做修改。

  以上步骤的第一种情况是直接在Allocate函数中调用内存申请函数AllocateNewBlock实现,而第二和第三种情况的判断及内存分配则是在Allocate函数中调用AllocateFallback实现。一下为Allocate函数的源码:

inline char* Arena::Allocate(size_t bytes) {assert(bytes > 0);//上述第一种情况if (bytes <= alloc_bytes_remaining_) {char* result = alloc_ptr_;alloc_ptr_ += bytes;alloc_bytes_remaining_ -= bytes;return result;}//上述第二及第三种情况,调用AllocateFallback函数实现return AllocateFallback(bytes);
}

AllocateFallback

  此函数就是用来处理上述的第二或第三中情况。基本流程就是判断传入的bytes的大小以执行不同的操作,这样操作的理由是可以减少分配内存的次数,使得不管我们申请多大的内存,Arena都只需要为我们分配一次内存(而不会将一块内存分配在两个block中导致分配两次内存)。

源码如下:

char* Arena::AllocateFallback(size_t bytes) {//上述第三种情况,直接分配大小为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;}//上述第二种情况,重新创建一个block,并将相关指针和值转换为新block上的状态// 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;
}

​   笔者保留了源码中的英文注释,因为这两段英文注释非常清楚地解释了Google的工程师为什么要区分第二、第三种情况:由于我们每次重新创建一个block时,原来block中剩余的内存空间实际上是使用不到的,这样不可避免会造成内存的浪费情况,若我们所需分配的内存大小大于kBlockSize / 4时并不创建新的block而是直接分配一块相应大小的内存块时,这样可以保证每一个block上浪费的内存块的大小始终小于kBlockSize / 4;

​   若不理解为什么这样的设计每一块浪费的内存始终小于kBlockSize / 4,大家可尝试动手举一个浪费的内存块大于kBlockSize / 4的例子,这样也许更容易理解。

​   理解了Arena中基本的内存分配方式,我们再来看看Arena在内存申请是做了哪些事。

AllocateNewBlock

​   AllocateNewBlock是Arena向系统申请内存的函数。这个函数运用了new运算符向系统申请内存,并同时更新了Arena的已分配内存数memory_usage_

​   需要注意的是,因为存在分配内存块大于kBlockSize / 4的特殊处理方法,在AllocateNewBlock函数中并不会对alloc_ptr_指针以及alloc_bytes_remaining_做调整,这个指针以及值的调整操作应当是在调用AllocateNewBlock函数处决定是否执行。

​   AllocateNewBlock函数的流程简单来说就是:申请内存 --> 将新的内存块加入blocks_中 --> 修改memory_usage_的值 --> 返回所申请的内存块的指针。函数并不难理解,源代码如下:

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的内存分配基本原理之后,我们最后来看看Arena按对齐方式分配内存的实现原理:AllocateAligned函数。

AllocateAligned

​  在这个函数中,首先定义需要对齐的字节数,按照机器的void*的大小来对齐,若超过8字节,则最多按照8字节对齐:

const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;

​ ​  接下来,计算当前分配的内存 % 对齐字节数,也就是计算出按照当前对齐方式分配内存的话比对齐界限多了多少字节,并将这个值存放在current_mod中,假设align = 8,则current_mod的意义如图所示:

​  得到这个偏差之后,我们就能轻易得到若需要对齐还差多少字节了,我们将这个差值保存在needed中,上图例中,needed = 8 - current_mod = 5

​ ​  接下来我们将传入的bytesneeded相加,就能的得到我们对齐之后总共需要分配多少字节内存了,同样我们按照最开始所提到的三个策略进行分配进行分配,但是需要注意的是,若 对齐总共需要分配的内存 < 剩余的块内存,那么调用AllocateFallback(bytes)函数,此处传入的参数为bytes而不是对气后的总值,是因为使用AllocateFallback函数向系统索要的内存永远是字节对齐的(AllocateFallback always returned aligned memory)。

​ ​  老规矩贴出函数源码:

char* Arena::AllocateAligned(size_t bytes) {//设置对齐的字节数,按照机器的 void* 的大小来对齐,若超过8字节,则最多按照8字节对齐。const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;// 字节对齐必须是 2 的次幂static_assert((align & (align - 1)) == 0,"Pointer size should be a power of 2");//A & (B - 1) = A % B//通过以上公式求出current_mod的值,并强制转换为uinitptr_t类型//uinitptr_t为当前机器指针大小size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);//求出与对齐量的偏差值size_t slop = (current_mod == 0 ? 0 : align - current_mod);//needed为实际应当分配的内存size_t needed = bytes + slop;char* result;//这里的if-else操作可见以上分析if (needed <= alloc_bytes_remaining_) {result = alloc_ptr_ + slop;alloc_ptr_ += needed;alloc_bytes_remaining_ -= needed;} else {// AllocateFallback always returned aligned memoryresult = AllocateFallback(bytes);}assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);return result;
}

总结

​ 相比于Nginx的内存池实现方式,Arena的策略并没有区分大块内存和小块内存。但同样Arena并没有给出类似于freedelete之类的函数,相关的操作在Arena的析构函数中执行。所以通过Arena来管理程序中各个模块的内存使用,可以有效防止内存泄露的问题。

leveldb:Arena内存池相关推荐

  1. Arena内存池简介

    一. 什么是内存池? 内存池(Memory Pool)是一种内存分配方式,又被称为固定大小区块规划(fixed-size-blocks allocation). 通常我们习惯直接使用new.mallo ...

  2. 当Java虚拟机遇上Linux Arena内存池

    作者简介 刘韬,云和恩墨中间件服务交付团队专家 Java开发出身,10年WebLogic相关开发.运维工作经验,熟悉SOA.现代业务系统架构中各层组件,尤其擅长故障处理.性能优化等工作. 故障案例一 ...

  3. 经典的arena内存池实现-levelDB的内存池实现

    Arena实现 arena可以说是解决内存碎片的利器,虽然有很多前辈说,要相信malloc的实现,你能想到的那些问题在设计Malloc的时候肯定都考虑到了.是的你可以相信malloc的实现,但是你不能 ...

  4. LevelDB源码解析(1) Arena内存分配器

    你也可以通过我的独立博客 -- www.huliujia.com 获取本篇文章 背景 LevelDB中需要频繁申请和释放内存,如果直接使用系统的new/delete或者malloc/free接口申请和 ...

  5. leveldb Arena 分析

    leveldb Arena 分析 版权声明:本文为 cheng-zhi 原创文章,可以随意转载,但必须在明确位置注明出处! Arena Arena 是 leveldb 项目里面使用的轻量级的内存池对象 ...

  6. LevelDB Arena源码分析

    欢迎访问我的个人博客 https://vincillau.github.io/ 文章目录 欢迎访问我的个人博客 https://vincillau.github.io/ LevelDB Arena源码 ...

  7. Netty技术细节源码分析-内存池之PoolChunk设计与实现

    该文所涉及的netty源码版本为4.1.16. 在一开始需要明确的几个概念 在Netty的内存池的PoolChunk中,先要明确以下几个概念. page: page是chunk中所能申请到的最小内存单 ...

  8. Netty 的 内存池 是如何实现的

    对于今天的源码剖析,你可以带着下面这么几个问题: 1 PoolArena中的PoolSubpage数组和PoolChunk中的PoolSubpage数组有什么关联? 2 PoolThreadCache ...

  9. bytebuf池_PooledByteBuf内存池-------这个我现在不太懂

    转载自:http://blog.csdn.net/youaremoon/article/details/47910971 http://blog.csdn.net/youaremoon/article ...

最新文章

  1. 走向DBA[MSSQL篇] 面试官最喜欢的问题 ----索引+C#面试题客串
  2. 围棋棋盘上的波粒二象性
  3. 英特尔布局5G 提供端到端的解决方案
  4. 微型计算机的系统组成图,微型计算机系统结构图.doc
  5. 计算机动画制作 教学设计,《设置动画效果》教学设计
  6. MySQL之 分库分表
  7. 59. Spiral Matrix Spiral Matrix II
  8. window-linux移植
  9. 【sklearn第十五讲】决策树之回归篇
  10. CTP: 找ActionDay 和TradingDay说点事
  11. 阿里月饼事件,猿方怎么看?
  12. 如何重新安装正版Win10
  13. 关于travis scott的网名_文案| 关于【太阳】的惊艳句子
  14. linux输入法怎么关闭,关于Ubuntu 7.10的缺省输入法Xim
  15. 第三次郑州.NET俱乐部活动总结
  16. 计算机算法在生物信息学中的应用,引力场算法及其在生物信息学中的应用
  17. windows下ITIM admin API开发环境配置,部署至tomcat
  18. SAAS云服务三种模式
  19. Markdown基本使用
  20. 机械键盘(可编程键盘)无效、串键、连键问题解决偏方

热门文章

  1. SPI方式读取汉字字库芯片
  2. 牛客网 石家庄铁道大学新生选拔赛
  3. 正则 以小写英文字母开头,且只能包含英文字母、数字、下划线
  4. python抽取某个时间段的数据_python 批量提取excel 指定时间段的数据
  5. 《程序员延寿指南》的极简操作指南
  6. 大数据时代时代舍恩伯格书资源_疫情宅家,这些大数据书籍可以好好读读
  7. 《把时间当作朋友》第1章读后感(二)
  8. C#中Trim的功能介绍
  9. Altium designer18系列教程一 建工程和封装介绍
  10. gdb调试器之测不准原则