对于大多数开发人员而言,系统的内存分配就是一个黑盒子,就是几个API的调用。有你就给我,没有我就想别的办法。css
来UC前,我就是这样以为的。实际深刻进去时,才发现这个领域里也是百家争鸣。很是热闹。有操做系统层面的内存分配器(Memory Allocator)。有应用程序层面的,有为实时系统设计的,有为服务程序设计的。html
但他们的目的倒是同样的。平衡内存分配的性能和提升内存使用的效率。web
从浏览器开发的角度看,手机内存的增加速度相对于网页内容的增加仍然仅仅是温饱水平,像Android自己就是用内存大户。另外一个Low Memory Killer, 必定要优化内存占用。总体上对策就是两点:一是能不用就不用,代码里可能隐藏着很是多没必要要内存分配。特别留意那些中间量。二是能少用就少用,特别避免频繁分配。因为那样仅仅会添加内存碎片,到了极端时即便仍有内存可用,也分配不出来了。还有一个选项: 换个内存分配器。这样一是假设内存分配器优良就可以缓解内存碎片,也可以在出现OOM时控制程序的行为,崩与不崩、崩在哪里就可以本身控制了。算法
近期因为工做缘由。涉及到了小内存分配器,因此作了一些粗浅的学习。没有完整的阅读代码。也没有进行透彻的測试,仅仅是写个总结以及相关的文档放在这里,备查。数组
首先一般使用的内存分配器,即malloc/free函数并不是系统提供的,而是C标准库提供的。也被称为动态内存分配器。分配器从操做系统拿内存(虚拟内存)时是以页为单位(通常是4KB,调用sbrk或mmap), 而后再自行管理。浏览器
上面也提到了,内存分配器面对的是两个核心问题: 效能和性能(或称为吞吐量Throughoutput)。 前者保证随时有内存可用,后者保证服务时间短、不拖后腿。数据结构
对于一个系统进程而言,面对OOM(Out Of Memory)问题,排除程序使用内存的Bug外,会有两个缘由:
1.系统真的没有内存可用了。多线程
2.内存分配浪费了大量空间。尽管有大量零散的可用空间,却没法合并提供出来使用。 前者才是真正的OOM, 后者就是内存碎片(Fragmentation)问题了。ide
libc里的malloc遇到分配失败时,默认会abort掉进程。也就是崩掉(CRASH)了。函数
假设系统支持mallopt就有机会改变这个行为。惋惜Android尚未支持。
浏览器在载入、解析、渲染页面的时候,会分配大量的小对象,看张图就明确了:
上图中模轴为对象大小,纵轴则为申请分配的次数。假设内存都以页为单位申请,就简单,也就不需要分配器。就是那些小对象,占用很少,使用频繁,很是easy形成页内没法再继续使用的碎片(Internal Fragmentation)。
对于性能,内存分配是次于I/O的一个瓶径。
尽管绝大多数状况下都相安无事。但内存分配器有一个重要的指标,即上限(bounded limits)。尽管平均值看起很是好,但一旦遇到最坏的状况(wrost case)时。能不能保证性能?特别是多线程下,内存分配、释放的性能常常受到加锁的影响。有些分配器(如ptmalloc)过于考虑性能,而没法使线程间的内存共享,各自占去一块,反而减小了内存使用的效率。
这些问题一直存在。不一样的人针对不一样的场景设计出了不一样的分配器算法(DSA, Dynamic Storage Algorithms, 是以应用的角度来看的)。而且差点儿每个都说本身比别人强。比方:
1. dlmalloc/ptmalloc/ptmallocX C标准库提供的分配器, 也是应用程序默认使用的malloc/free等函数。
2. tcmalloc 出自Google, WebKit/Chrome中应用。
3. bmalloc 毕竟Chrome和WebKit越走越远。因此Apple在WebKit最新代码(2014-04)里提供了新的分配器,号称远远超过 TCMalloc, 至少是在性能上。
4. jemalloc 本来是为FreeBSD开发的,后来Firefox浏览器和FaceBook的服务端都加以应用,它自身也在这些应用中获得了大幅提高。
5. Hoard 一个专为多线程优化的分配器, 做者是大学教授。有一些独特的技术。Mac OS X中的malloc就有參考事实上现进行优化。
*WebKit另外专为Render Object提供了一个所谓的Plain Old Data Areana的类,也算是一个Memory Pool的实现(PODIntervalTree, PODArena)。
分配器这么多,其核心思想相似,仅仅是差在算法和metadata存储上。附13提供的论文中有比較全面的总结,可以翻看一下。
内存分配器的核心思想归纳起来三条:
1. 基本功能:首先将内存区(Memory Pool)以最小单位(chunk)定义出来。而后区分对象大小分别管理内存。小内存分红若干类(size class),专门用来分配固定大小的内存块,并用一个表管理起来。减小内部碎片(internal fragmentation)。大内存则以页为单位管理, 配合小对象所在的页,减小碎片。设计一个好的存储方案。即metadata的存储。减小对内存的占用。
同一时候优化内存信息的存储,以使对每个size class或大内存区域的訪问的性能最优且有上限(bounded limits)。
比方dlmalloc定义的是一个个bins(同size class)来存储不一样大小的内存块:
2. 回收及预測功能: 当释放内存时。要可以合并小内存为大内存,依据一些条件,该保留的就保留起来,在下次使用时可以高速的响应。不需要保留时。则释放回系统。避免长期占用。
3. 优化多线程下性能问题:针对多线程环境下,每个线程可以独立占有一段内存区间。被称为TLS(Thread Local Storage)。这样线程内操做时可以不加锁,提升性能。
下图是MSDN上贴出的关于TLS的原理图,可以參考:
*另外測试工具也是不可缺乏,比方tcmalloc的heap profile, jemalloc则结合valgrind。FireFox在移植jemalloc到Android时。特别关掉了TLS,想必是考虑到它对于线程单一应用的反作用。
上面这些思路对于各个分配器而言基本是一致,但详细怎样组织size classes, 假设以一个固定步长,必将造成一个巨大且效率低下的表。缘由參考第一张图就明确了。
很是多年前,就有专门的论文对此作了评定(连接)。另外还有怎样定位内存块? 怎样解决多线程下的false cache line问题? 不一样的分配器使用了不一样的算法和数据结构来实现。它们所使用的算法统称为DSA, Dynamic Storage Algorithms。
详细的算法实现可以在如下的參考列表中找到相应的文档, 也可以先看附16。文中分别对DSA Algorithms和DSA Operational Model作了描写叙述,归纳的很是好。会有一个总体的印象。做者将DSA算法分为五类:
1. Sequential Fit
是基于一个单向或双向链表管理各个blocks的基础算法。因为和blocks的个数有关。性能比較差。这一类算法包含Fast-Fit, First-Fit, Next-Fit, and Worst-Fit。
2. Segregated Free List (离散式空暇列表)
使用一个数组,每个元素是存储特定大小内存块的链表。它们所表明的大小并不是连续的,因此称为离散。经典的dlmalloc使用的就是这个算法。数据元素,參照上面的图就可以理解了。TLSF算法则是基于此进行了改进。
3. Buddy System
这是由一代大师Donald Knuth提出。兴许产生不少的改进版本号。最大的做用是解决外部碎片(external fragmentation), 详细的算法。參考这篇(浅析Linux内核内存管理之Buddy System)。
4. Indexed Fit
以某种数据结构为每个block创建索引,以求可以高速存取。
通常以一个二叉树结构实现。比方使用Balanced Tree的Best Fit allocator, 以及基于Cartesian tree 的Stephenson Fast-Fit allocator。这类算法的性能比較高,也比較稳定。
5. Bitmap Fit
这类算法仅仅是索引方法不一样,使用以位图式字节表示存储单元的状态。它的优势是使用一小块连续的内存,响应性能更好。
Half-Fit就属于这类算法。
随着技术演进。现在主流的allocators, 基本上都是综合运用了两类以上的算法。
另一些基础算法也是相似的。比方以二叉树组织列表的算法,也就是in-place, 笛卡尔树 和red-block的差别。在线程上。则因为实现的不一样,会致使内存占用的差别。比方jemalloc在释放时,并不需要在原来的分配线程运行释放。仅仅是被放回到分配线程的free list中去。ptmalloc则必须回到分配线程里运行释放,性能就相对弱一些。
tcmalloc则设计了算法。让一个线程可以从它的邻居那里偷一些空间来(这个过程称为transfer cache)。这样可以有效地利用线程间的内存。
ptmalloc 劣势:多线程下的性能及内存占用(线程间内存没法共享),而且内存用于存储metadata开销较大。在小内存分配上浪费比較多。
优点:算是标准实现。
tcmalloc 劣势:因为算法的设计,占用的内存较大。优点:多线程下的性能。參考附6。
jemalloc 优点: 内存碎片率低。多核下性能较tcmalloc更好。參考附17。
时间有限,没有再深刻研究,后面有空再补充一下。在实际应用中,仍是有一些參数可以调整的,前提是要熟悉事实上现,特别是性能评估的方法。
转载请注明出处: http://blog.csdn.net/horkychen
这是我列的最长的參考清单了。前人的确已经作了很是多的研究,我对当中内容仅仅是泛读,并不是所有内容都相关,仅仅是以为有些内容可以相互应证就也列进来了。
1. jemalloc关于使用red-block tree的反思 [连接]
文章公布于2008年。做者在2009年将其应用于FaceBook时。则是进行了算法上优化。
2. 2011年jemalloc做者在FaceBook应用jemalloc后撰文介绍了jemalloc的核心算法及在Facebook上应用效果。
[连接] [早期的论文,有不少其余的细节]
3. Android碎片化的度量 经过改造ROM作的实验。
4. Hoard Offical [连接]
5. Mac OS上malloc是怎么工做的[连接]
6. 关于WebKit应用tcmalloc的对照[连接]
7. How tcmalloc works[连接] [中文翻译]
8. TCMalloc源码分析,很是不错资料。
做者的站点还有其余干货值得一读。[连接]
9. dlmalloc早期的技术文档,讲述了其核心算法。[连接]
10. ptmalloc源码分析,讲的很是系统。很是值得一读。[CSDN下载连接]
11. 介绍jemalloc的资料《更好的内存管理-jemalloc》[连接]
12. 替换系统malloc的四种方法 [连接]
13. 介绍针对实时系统进行优化的内存分配算法TLSF,当中对动态分配算法(DSA)作了总结。[连接]
14. 维基百科上关于Thread Local Storage的说明, 或许你能感觉到技术的相通性。[连接]
15. 针对实时系统进行各类分配算法的对照,可以结合13一块儿看。
[连接]
16. ptmalloc,tcmalloc和jemalloc内存分配策略研究。[连接]
17. Firefox3使用jemalloc后的总结,可以看到Firefox优化的思路。[连接] [Firefox使用的源码]
18. Chromimum Project: Out of memory handling, 里面有不错的观点。 [连接]