总结起来整个过程大概是这样的,假设我们向系统要x大小的内存,
(1)x大于128byte,用第一级配置器直接向系统malloc,至于不成功的处理,过程仿照new,通过set_new_handler来处理,直到成功返回相应大小的内存或者是抛出异常或者是干脆结束运行;
(2)x小于128byte,用第二级配置器向内存池相应的free_list要内存,如果该freelist上面没有空闲块了,
(2.1)从内存池里面要内存,差不多要的是<=20个相应freelist大小的块,如果要不到:
(2.2)从系统的heap里面要内存给到内存池,换算的标准是bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4),这时使用的是系统的malloc,如果要不到:
(2.3)从比较大的freelist那里要内存到内存池,如果还是要不到:
(2.4)从系统的heap里面要内存给到内存池,换算标准跟2.2一样,但是这时候使用的是第一级配置器的allocate,主要是看看能不能通过out_of_memory机制得到一点内存。所以,freelist总是从内存池里要内存的,而内存池可能从freelist也可能从系统heap那里要内存。
sgi stl的alloc的主要开销就在于管理这些小内存,管理这些小内存的主要开销就在于,每次freelist上的内存块用完了,需要重新要空间,然后建立起这个list来。freelist上的内存,会一直保留着直到程序退出才还给系统。但这不会产生内存泄漏,一来是管理的都是小内存,二来是,占用的内存只会是整个程序运行过程中小内存占用量最大的那一刻所占用的内存。
SGI STL的alloc是一种内存管理机制,用于管理小内存块的分配,以减少内存碎片。后来我又看了另外一些内存管理机制(利用对象管理资源),包括智能指针的RAII,用于复杂但可能过程不是很久的算法内存管理AutoFreeAlloc,ScopeAlloc,以及boost中的pool跟object_pool。总的来说,内存管理的基本要求就是:不重不漏,既不重复delete,也不漏掉delete。
首先是智能指针,智能指针的基本思想是利用编译器在对象离开作用范围后自动调用析构函数来实现内存的自动释放,即通过对象来管理资源,把指针封装成一个对象,在其构造、析构、赋值函数中,实现对内存的管理。最简单的是auto_ptr,你把指针给了它,它就在析构的时候把指针指向的内存释放掉。可是我要把指针赋值给其他人怎么办?它说,哦,那没办法,被赋值的那个人接管了你的内存,你就退休指向NULL吧。这显然给指针的应用带来了相当大的不便,想想,在用裸指针时,用temp = head, head = head->next这样的状况是多么常见,用容器存放指针也是常有的事(存放到容器是一种赋值行为)。那就使用share_ptr吧。share_ptr中记录了一块内存的reference_count,在构造时其为一,在复制构造、赋值时修改reference_count,在析构时reference_count--,如果reference_count为0,那么就把内存给释放了。在多线程中,reference_count便成了一个竞争资源,因此可能会成为瓶颈。另外,所谓的这个“把内存给释放了”,其实也可能只是一种指定的操作,例如把它放到freelist去啊之类的,是可以自定义的。
接下来的内容我基本是从许式伟的博客(http://blog.csdn.net/xushiweizh/article/category/265099)看到的,因为AutoFreeAlloc和ScopeAlloc是他们团队做的内存管理库中的一部分。AutoFreeAlloc的基本思想可以用下图表示:
因此在一般情况下,使用AutoFreeAlloc为底层的NEW的开销就是对m_end的移动,或者重新开辟一块大内存进行管理,而这种NEW不需要DELETE,当算法结束的时候通过clear把AutoFreeAlloc管理的内存链表释放掉。
情况一:
情况二:
情况三:
ScopeAlloc跟AutoFreeAlloc的区别主要在于,AutoFreeAlloc是直接从系统申请的内存,用的是malloc/free,而ScopeAlloc是从内存池里申请的内存,用的是使用的内存池ProxyBlockPool::allocate/deallocate。
Cpp代码 [url=][/url]
- typedef AutoFreeAllocT<roxyBlockPool> ScopeAlloc;
- typedef AutoFreeAllocT<DefaultStaticAlloc> AutoFreeAlloc;
内存池
经典的内存池技术是一种用于分配大小相同的小对象的技术,它通常涉及两个常量:MemBlockSize和ItemSize,就是说,每次申请的内存大小固定为ItemSize,当池中没有空间的ItemSize块时,从系统heap中申请MemBlockSize大小的内存块,跟原先池中的大小块串在一起,而MemBlockSize的块中也要相应建立起一条freelist,因此,内存池还涉及另外两个指针变量:pFreeNodeHeader,指向freelist的头部,当需要一个对象时,从pFreeNodeHeader取下一个,当然,如果pFreeNodeHeader为空,说明没有空闲的块了;另一个为pMemBlockHeader,最后要把所有的内存释放时,从pMemBlockHeader处开始释放。
boost::pool对经典内存池的改进:
(1)MemBlockSize不再是固定的,而是采用了预测模型,第一次申请时,MemBlockSize=ItemSize*32,第二次为ItemSize*64等等,就像std::vector的增长模型。
(2)增加了ordered_free(void* p)。ordered_free与free的差别在于,free的时候,只是简单地把这个item放回freelist的头部,而ordered_free假设这些item是有一定的顺序的,因此返回item的时候会找到一个合适的位置放置item(指的是在链表中的位置)。
boost:bject_pool支持手工释放内存和自动回收内存(并自动执行析构函数),从而保证当它离开作用范围后不会产生内存泄漏,为了判断block中的节点是否为free,要求其在获取、释放内存的时候必须使用ordered_malloc和ordered_free。这样子,当object_pool离开作用范围而调用析构函数~object_pool时,它遍历所有的内存块MemBlock,并遍历其中所有结点,如果该结点不出现在free_list中,那么它就是未被释放的,因此要执行该结点的析构函数,如果free跟malloc是有序的,我们就能在线性时间内完成自动释放。 |