本文章由vector03原创, 转载请注明出处.

邮箱地址: mmzsmm@163.com, 欢迎来信讨论.

 

 

3. 分配及实现

本章节介绍dlmalloc的分配算法和实现.由于存在多mspace的情况, dlmalloc使用了两套API.一套相应默认的mspace,以dl前缀开头,如dlmalloc,
dlrealloc等.假设创建了自己定义的mspace,则使用mspace开头的API,如mspace_malloc,
mspace_realloc等.但两套API在基础算法上是一致的.我们就以默认的API为主要对象介绍.

3.1 算法概览

事实上, dlmalloc虽然复杂,核心算法却非常简单,假设有前面章节的基础非常easy就能看懂.

核心分配算法针对small request和large request概括起来各五句话(注意这里的分配请求大小都是经过align和padding处理后的大小),相应small
request(<256字节),

1.        
首先在分配请求相应大小的分箱以及更大一级分箱中查找, 假设有则返回,否则进入下一步.选择这两个分箱由于它们最接近分配目标大小,且剩余部分都无法单独成为一个chunk
(原文中称之为remainderless chunk).

2.        
假设dv大小足够满足,则分割dv chunk,否则进入下一步.

3.        
在全部分箱范围内查找(包括small bins和tree bins),找到能够满足需求的最小的chunk,分割,将剩余部分指定为新的dv,否则进入下一步.

4.        
假设top chunk满足需求,则分割top,否则进入下一步.

5.        
从系统获取内存并使用它.

相应large request,

1.        
从tree bins中查找最小可用的tchunk,假设其比dv更加适合(更接近目标大小),就使用该chunk.假设其剩余部分超过最小可分配chunk,则分割它.否则进入下一步.

2.        
假设dv满足需求,且比不论什么分箱中的chunk更适合,则使用dv,否则进入下一步.

3.        
假设top满足需求,则使用top,否则进入下一步.

4.        
假设分配请求大于mmap_threshold阈值,则直接通过mmap分配,否则进入下一步.

5.        
从系统获取内存并使用它.

从类型上, dlmalloc属于best-fit型分配器,仅仅是Doug Lea在此基础上做了诸多优化.本质上都是本着物尽其用的思想来挑选合适的free
chunk, 仅仅有当不能首先满足时, dlmalloc会通过dv和top来做进一步的挑选,这就最大限度的减小了内部碎片产生.同一时候dv和top的存在也能比較有效的降低外部碎片.

而假设外部请求过大, dlmalloc不是优先获取系统内存后分配,反而倾向于直接通过mmap获取.原因在于位于top的free
chunk有可能由于相邻高地址的alloced chunk而一直无法释放.假设dlmalloc向系统申请了大块内存,即便被应用程序free,也可能由于auto
trmming失败而导致它们长期驻留在top space中.而直接mmap的优点就是随时能够将这些huge chunk返回给系统,仅仅要应用程序决定不再使用它们.

以下是更详细的代码分析,

基本上还是比較好理解的,
以下对一些地方做展开说明,

1.        
Line5491, 这里假设使用lock, 会在開始确认一些全局參数是否初始化.这些參数保存在名为mparams的全局变量里,类型为malloc_params,包括交叉检查的magic,当前系统页面大小,设定的粒度大小,
mmap的阈值, trimming阈值以及默认的mspace參数.而且以magic作为參数初始化的标志.

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

2.        
Line4595, PREACTION和POSTACTION成对出现,就是加锁和解锁.由于是平台相关的,针对不同系统须要有详细的实现.从这里事实上也能够看出dlmalloc对多线程条件下的分配设计的还是比較简陋的,关注的还是单线程下分配算法的实现.

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

3.        
Line4619, 这里是一个对double link list首节点的删除操作,且假设list为空就更新small map.注意,
dlmalloc为了提高list处理速度,是设计了头节点的,因此这个first chunk并非头节点,而是其前一个节点.这个以前在前面的章节提到过,能够通过这里的详细实现看到这些优势,当中B指代list头节点,
P是须要删除的节点.

 

4.        
Line4663, 是替换dv的过程,旧dv假设还存在,会送回到分箱系统中管理,而新的chunk作为其替代.
M指mstate, P是继任dv, S为继任dv大小.

这里insert_small_chunk是前面删除的反向操作,实现例如以下,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

在插入的同一时候,
会对small map进行维护操作.

3.2 tmalloc_small

tmalloc_small是在tree bins中分配small chunk的子函数.用于small
request的核心分配算法3,即当remainderless和dv都无法满足,且剩余small bins也没有free
chunk时,从tree bins中搜索.

代码本身事实上比較easy理解,
源代码凝视例如以下,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

两点说明,

1.        
Line4537, 寻找DST最小节点通过宏leftmost_child完毕,该宏的定义例如以下,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

这里涉及到最小节点的遍历.
我们知道, 对于BST来说,根节点与左右子树有严格的排序关系,因此查找最小节点就是从根节点出发向左子树步进,一直遇到左子树为null停止的过程.

但如2.2.5小节中所述, DST本就不是一棵排序树,根节点同子树间不能确定大小关系,相比之下获取最小节点就更困难一些.但能够确定的一点是,同一级level中,越靠近左側的子树节点就越小,因此我们能够大致圈定最小节点出现的范围,例如以下,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

上图中用颜色标记了每一级level最左側的节点(不限于左子树或右子树),虽然临时还无法断定哪一个是最小节点,但它肯定出如今从A到E的路径上.所以DST的搜索路径为,从根节点出发,一路向左子树步进,若遇到左子树为空,就转头向右子树,一直遇到左右子树都为空停止.换句话说,沿着整棵DST的最左側边缘走,如图所看到的

关于这一点, 我想应该是DST最大的缺陷,由于不管怎样,遍历的次数是与树高相关的,上图中最小节点可能出如今位置C,但你须要完毕每一次比較才干最后下决定.只是好在对于size_t等于4字节的系统,树高最多也仅仅有32.不管怎样这比线性查找还是要快得多了.

2.        
Line4551, 与DST的删除操作相关.由于unlink_large_chunk宏的代码比較长,还是先说明一下节点删除算法.基本上,
Doug Lea的DST删除算法分为三个步骤,

第一步, 推断待删除节点X是否存在同样大小的兄弟节点.假设有,仅仅要简单的将其从双链表上摘除再又一次接好链表就可以.如图,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

第二步, 假设节点X所在位置仅仅有其一个节点,就须要选出一个继任节点R以替代X空缺的位置,同一时候还要保证DST的性质.由于DST也属于前缀树(prefix
tree)的一种,因此子树节点提升level是非常easy的,但降低level情况就相对复杂了.比方,子树节点前缀为0101,能够提升为010,但假设下降为0101x…x就必须參考其它子树的情况.如图,

这里假设我们选择R作为继任节点,则原节点X的左子树节点L就要改变其level.这时必须參考子树R的情况,为L寻找一个合适的插入点.假设R的内部非常复杂,这个过程就会相对漫长.

因此Doug Lea取了个巧,他选择了right-most叶子节点作为X的继任节点.既然是叶节点,仅仅须要简单的提升level就可以,其它子树节点的level和位置都不会发生不论什么变化,于是就绕过了上述问题.选择right-most的原因还在于,
dlmalloc在遍历best-fit节点时,会依照left-most的路径查找,导致多数情况下,左子树节点数少于右子树.为了平衡左右子树,同一时候削减子树高度,选择right-most相比更为合适,如图,

上图中查找到子树X的right-most节点R,用其替换X空缺的位置,能够看到L节点等子树节点位置没有不论什么变化.同一时候,改变R的位置平衡了左至右子树,让DST总体更均衡.

第三步, 这里就比較清楚了,仅仅须要又一次连接继任节点与原X的父节点和左右子树节点就可以.

整个过程的源代码凝视例如以下,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

3.3 tmalloc_large

该函数是在tree bins中分配large request的子函数.与tmalloc_small略有差别,
large request并非寻找最小节点,而是best-fit节点,即一个大于等于期望值的最小节点.

基本算法例如以下,

1.        
以分配请求大小nb作为key值进行基值检索,并做两点记录.一是记录最接近的候选节点v,还有一个记录当前近期的未被遍历的右子树节点rst
(The deepest untaken right subtree).同一时候假设找到同样大小的chunk则马上返回.

2.        
若已遍历到子树的最下层, 则返回记录的rst子树节点,从这个位置開始进行left-most遍历,这里同tmalloc_small中寻找最小子树节点是一致的.

3.        
若找不到可用节点, 则从treemap中寻找最小可用分箱,从可用分箱中寻找.

4.        
若dv比候选节点v更适合,则直接返回0,否则分割候选节点,并终于返回payload.

在同一分箱内的搜索过程如图所看到的,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

在一个分箱内,
搜索best-fit节点依照从A到F的顺序运行.当中A-D属于基值检索,以nb的前缀为key值,而E-F则依照left-most检索,由于E子树是当前分箱中大于目标值的最小子树,仅仅要找到最小节点就可以.从这里也能够看出这个算法本质上非常简单,就是先依照前缀寻找最接近的目标节点,假设没有则扩大范围在大于目标值的最小子树中搜索,还没有再到最接近的更大的分箱中查找,直到找到为止.

代码凝视例如以下,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

Line4465, Line4480, Line4490,这三处地方事实上就是对nb掩码逐bit位的測试操作,以进行基值检索.

该宏展开例如以下,

看上去有些复杂,
事实上就是除最高有效位以及次最高有效位之外, 将兴许bit位移动到msb端.之后每次循环就取出一位进行检測.例如以下图所看到的,

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yMDM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" />

略微不好理解的就是i >> 1的作用.回想一下2.2.4小节中tree bins索引寻址的说明,这里就是computer_tree_index的逆运算.结果是不算末尾8bit,最高有效位的位号.这里减2的原因是最高有效和次高有效位用于计算分箱号,因此不计入key值.位移后获得的掩码在检測时会又一次右移至最低位,并提取以决定是向左子树还是右子树步进.在整个循环中会不断左移掩码以保证遍历持续进行,直至达到最底层子树节点.

事实上这个宏Doug Lea搞得有点麻烦,这个运算用CLZ指令加2就能获得同样的结果.我猜Doug
Lea不这样写的原因可能是尽量降低各个平台的差别,或者纯粹是他懒得再分别写四种实现.

dlmalloc 2.8.6 源代码具体解释(5)的更多相关文章

  1. dlmalloc 2.8.6 源代码具体解释(6)

    本文章由vector03原创, 转载请注明出处. 邮箱地址: mmzsmm@163.com, 欢迎来信讨论. 3.4 sys_alloc sys_alloc是dlmalloc中向系统获取内存的主要接口 ...

  2. Spring IOC源代码具体解释之容器初始化

    Spring IOC源代码具体解释之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比較典型的代码 ClassPathResource res = new C ...

  3. Spring IOC源代码具体解释之容器依赖注入

    Spring IOC源代码具体解释之容器依赖注入 上一篇博客中介绍了IOC容器的初始化.通过源代码分析大致了解了IOC容器初始化的一些知识.先简单回想下上篇的内容 加载bean定义文件的过程.这个过程 ...

  4. Spring IOC源代码具体解释之整体结构

    Spring ICO具体解释之整体结构 IOC介绍 IOC, spring的核心.贯穿Spring始终.直观的来说.就是由spring来负责控制对象的生命周期和对象间的关系,将对象之间的关系抽象出来. ...

  5. MQTT---HiveMQ源代码具体解释(四)插件载入

    源博客地址:http://blog.csdn.net/pipinet123 MQTT交流群:221405150 实现功能 将全部放在plugins文件夹下的全部符合plugin编写规范的plugin ...

  6. MQTT---HiveMQ源代码具体解释(一)概览

    源博客地址:http://blog.csdn.net/pipinet123 MQTT交流群:221405150 面向群体 想自己实现MQTT Broker的朋友 对现有开源的MQTT Broker或多 ...

  7. Android MediaScannerJNI源代码具体解释

    1.简单介绍 MediaScannerJNI的在MediaScanner中的地位可參考 Android MediaScanner 总纲 MediaScanner JNI文件名称: android_me ...

  8. MQTT---HiveMQ源代码具体解释(十八)Cluster-kryo与Serializer

    源博客地址:http://blog.csdn.net/pipinet123 MQTT交流群:221405150 既然是Cluster,node之间肯定是须要交互的,那么肯定是须要序列化和反序列化.Hi ...

  9. MQTT---HiveMQ源代码具体解释(七)Netty-SSL/NoSSL

    源博客地址:http://blog.csdn.net/pipinet123 MQTT交流群:221405150 实现功能 依据用户配置的不同的Listener(TcpListener.TlsTcpLi ...

随机推荐

  1. kibana 5.5 源码编译踩坑记录

    由于项目需要定制开发kibana,因此需要编译kibana,在开发环境下运行.   注意:必须下载kibana 5.5的源码才能正常编译,下载release或者snapshot版本是不行的,运行npm ...

  2. 集合 List和Set

    分别向Set集合以及List集合中添加“A”,“a”,“c”,“C”,“a”5个元素,观察重复值“a”能否在List集合以及Set集合中成功添加. List<String> ls=new ...

  3. SSM中使用POI实现excel的导入导出

    环境:导入POI对应的包 环境: Spring+SpringMVC+Mybatis POI对应的包 <dependency> <groupId>org.apache.poi&l ...

  4. 了解Linux的基础知识和一般概念

    1.GNU和GPL    GNU计划(又称革奴计划),是由Richard Stallman(理查德·斯托曼)在1983年9月27日公开发起的自由软件集体协作计划.它的目标是创建一套完全自由的操作系统. ...

  5. [JSOI2007]建筑抢修 优先队列 贪心

    Code: #include<cstdio> #include<algorithm> #include<cstring> #include<queue> ...

  6. iOS开发——剪切板

    剪切板的调用是很简单的,常用的就这两个,不管文字或是图片的粘贴复制,对剪切板的操作就是基于下面两个属性的: [UIPasteboard generalPasteboard].string [UIPas ...

  7. LightOJ-1341 Aladdin and the Flying Carpet 分解质因数(注意对大素数的优化)

    题目链接:https://cn.vjudge.net/problem/LightOJ-1341 题意 给出一个长方形的面积a 让你算整数边长的可能取值,并且两个边都大于给定数字b 思路 唯一分解定理: ...

  8. [NOIP2012提高组]国王游戏

    题目:洛谷P1080.Vijos P1779.codevs1198. 题目大意:国王和每个大臣左.右手各写了一个数.规定每个大臣得到的金币数为他前面所有人左手的数字的乘积除以他自己右手的数(向下取整) ...

  9. gcc/g++命令参数笔记

    1. gcc -E source_file.c -E,只执行到预编译.直接输出预编译结果. 2. gcc -S source_file.c -S,只执行到源代码到汇编代码的转换,输出汇编代码. 3. ...

  10. Mac上配置 Ruby on Rails和Git

    Ruby on Rails on Mac =============================================================================== ...