1 动态内存管理
一般来说,程序使用的内存是从一个大的内存池中获得的,我们通常称这个内存池为堆heap。
C++中,我们通过库函数来动态的分配/释放内存,例如malloc, free, new 和delete。
mallow分配一个给定大小的内存块然后将指针返回给程序员,程序员可以对这个内存块做任何操作。
new指令,首先分配对象大小的内存,然后调用该对象的构造函数(如果有且能够访问的话)。
动态分配内存的一般的方法是,程序向内存管理器(Memory manager)分配/释放一个或多个对象,内存管理器再向操作系统OS要求分配/释放一个或多个页。注意,分配器不会将整个页返回给程序,而是要多少给多少。剩下的部分(例如,没有给程序的部分)留给将来的内存请求。

2 分配器存在的问题
由于大多数的程序进行了大量的内存操作(分配/释放),我们对内存管理的期望是更快,更少的内存碎片,充分利用局部性locality,并能够方便的扩展到多核多处理器。
这里的局部性有两层意思,Temporal locality时间上的局部性,在一个相对短的时间内重复使用一块特定的资源,这里指内存;Spatial locality空间局部性,使用固定的存储区域来操作数据;Sequential locality连续局部性,空间局部性的一个特例,数据元素线性的存放和操作。
fragmentation内存碎片,指的是heap的内存由于内存分配算法的原因被整得四分五裂。而裂缝中的可用内存又无法满足用户的要求时,就造成了内存的浪费,解决的办法可以是做一个内存整理,就像我们常做的磁盘碎片整理一样,但是显然会耗费大量时间,而且这个整理的时机不太好把握,用户操作的指针也需要随着整理一起更新。
external fragmentation外部碎片,碎片发生在自己掌握的内存之外。
internal fragmentation内存碎片,碎片发生在自己掌握的内存之内,好像有点废话。这种碎片可能发生在将请求的内存大小调整到最接近的2的n次方,或者存储对象头时包含对象的大小。
 
3 管理可用内存 keeping track of free memory
如何管理这些可用的内存呢?有一种方法是管理一个链表组并将可用的内存加入到链表里,每个链表管理相同大小的内存块(例如一个链表管理8字节的内存块,一个链表管理16字节,都是2的倍数)。这种方法类似于best-fit算法,因为我们总可以找到最接近我们需要的内存,当然,区别就是这个链表组管理的不是任意大小的内存块,而是2的倍数。
另一个可能的技术叫做Big Bag of Pages(BiBOP)。这种技术是管理一个页组a bunch of pages,通常由类型的大小来分隔,这样每个页都有一个页头,页头中可以找到指向下一页的指针,而且可以得到指向当前页中第一个可用的内存块first free "slot" inside the page。给定页中对象的指针,我们可以通过简单的位与运算得到页头指针。通过一些处理,我们可以让BiBoP搜索管理内存的时间在O(1)。而且,我们从来不用合并链表中相邻的元素,这就是segregating by size classes的魅力所在。
 
4 定制内存分配
程序员可以定制内存分配来满足他们特定的要求。尽管对于大多数程序来说没有这种需求,但这种方式也比较普遍。当然,定制的目的就是替换OS的分配器,并提供一种优化版本来有效处理特定程序的特定数据结构。
理想的情况是我们希望新的分配器更快的分配内存(优先级一般),提供扩展功能并且尽量的小(优先级比较低)。定制分配器的典型例子有Apache webserver,GCC,STL和大多数数据库服务器。
不幸的是,定制分配器也有缺点,例如他们很少允许memory debugger。
 
5 分配器的种类
我们可以想象好几种内存分配器。例如,Pre-class分配器,但我们知道马上要一遍一遍的分配同样类型的对象时非常有用。这时,相比segregating by objects size(ie,为8,16...byte的对象维护一个链表),我们可以创建一个内存slot的链表,例如大小为18bytes。这个可以看作一些常用对象的内存池。Pre-class分配器非常快速和简单,但在空间上可能不是那么有效率。
 

<<未完待续>>
 posted on 2009-10-13 10:49  Kratos  阅读(587)  评论(0编辑  收藏  举报