VC6/BC5/G2.9(std::allocator)
上面这三个分配器都是基于::operator new和::operator delete实现的,并没有特殊优化.
G2.9 __pool_alloc
G2.9版本实现了一个内存池,用来减小malloc开销和cookie。先说一下cookie:
当我们每次向系统申请一块内存(堆)时,系统会在我们申请的内存两头放两个cookie,32位机器是4字节,64位机器是8个字节。
cookie的主要用途是:
- 标记申请内存块的大小,如果能想起来我们在delete的时候并没有给定指针的大小,那么系统是怎么知道那块内存有多大呢?通过指针前移4/8个字节,读取内存里的结果就可以。
- 标记此块内存是否被占用,上图中00000041的1就是用来标记此后的0x40内存是被占用的,如果想在此继续申请就会被拒绝。
其实malloc本身是一个内存池,我们申请是很快的,主要就是要避免cookie开销,试想一下,如果我们的对象中只有一个指针,那么cookie就占据了66%的空间,这是我们所不能容忍的。
下面通过侯捷大佬的课件回忆一下内存池的整个分配过程。
上图是总览,这个freelist总共有16个区块,每个区块负责不同大小的内存分配。区块之间的递增大小为8。
这里的freelist使用了嵌入式指针技术(embedding pointer)当区块没有被分配出去之前,结构体内存放的是指向下一区块的指针,分配时将指针值赋给下一个区块的指针,然后将对象信息直接覆盖掉指针。
这是对应代码中的定义。
下面来看一下具体的分配过程。在图中解释的十分清楚.
G2.9的内存管理通过自由链表构造一个内存池来控制,这样做会大量减少cookie的产生,但是也存在问题,比如最后一页,其实在我们的内存中还有大量剩余内存,但是对一个大区块,我们却无法合并区块来提供给用户,并且随着申请数量的加大,我们的累积量会越来愈大,严重情况下会影响其他进程合理的内存要求。
关于追加量为什么是累计量的4倍,一个可能的解释是编写此内存分配的程序员认为或者根据经验,内存申请会越来越多,我们提前准备好足够多的内存能避免这种情况。