以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- C++内存管理变革:通用型垃圾回收器 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=58485) |
-- 作者:卷积内核 -- 发布时间:1/24/2008 11:38:00 AM -- C++内存管理变革:通用型垃圾回收器 引言 在前文,我们引入了GC Allocator(具备垃圾回收能力的Allocator),并提供了一个实作:AutoFreeAlloc(详细内容参见《C++内存管理变革(2):最袖珍的垃圾回收器 - AutoFreeAlloc》)。 但是,如前所述,AutoFreeAlloc是有其特定的适用环境的(它对内存管理的环境进行了简化,这种简化环境是常见的。详细参阅《C++内存管理变革(3):另类内存管理 - AutoFreeAlloc典型应用》)。那么,在AutoFreeAlloc不能适用的情形下,我们可以有什么选择? 本文要讨论的,正是这样一个GC Allocator实作。它所抽象的内存管理的环境比之AutoFreeAlloc复杂许多,适用范围也广泛很多。这个GC Allocator我们称之为ScopeAlloc。 思路 理解ScopeAlloc的关键,在于理解我们对AutoFreeAlloc的模型所作的修正。我们设想一个算法的第i步骤比较复杂,其内存开销也 颇为可观,希望为步骤i引入一个私有存储(Private GC Allocator),以便哪些步骤i内部计算用的临时内存在该步骤结束时释放。示意图如下: 由于引入私有存储(Private GC Allocator),模型看起来就变得很复杂。上面这个图也许让你看晕了。不过没有关系,我们把上图中与步骤i相关的内容独立出来看,得到下图: 如图2显示,一个算法会有自己的私有存储(Private GC Allocator),也会使用外部公有的存储(Share GC Allocator)。之所以是这样,是因为算法的结果集(Result DOM)不能在算法结束时销毁,而应该返回出去。这我们大致可以用以下伪代码表示: ResultDOM* algorithm(InputArgs args, ScopeAlloc& shareAlloc){ ScopeAlloc privateAlloc(shareAlloc); ... ResultDOM* result = STD_NEW(shareAlloc, ResultDOM); ResultNode* node = STD_NEW(shareAlloc, ResultNode); result->addNode(node); ... TempVariable* temp = STD_NEW(privateAlloc, TempVariable); ... return result; } 挑战 ResultDOM* algorithm(InputArgs args, AutoFreeAlloc& shareAlloc){ AutoFreeAlloc privateAlloc; ... ResultDOM* result = STD_NEW(shareAlloc, ResultDOM); ResultNode* node = STD_NEW(shareAlloc, ResultNode); result->addNode(node); ... TempVariable* temp = STD_NEW(privateAlloc, TempVariable); ... return result; } 生成一个新的AutoFreeAlloc实例是一个比较费时的操作,其用户应注意做好内存管理的规划。而生成一个ScopeAlloc实例的开销很小,你甚至可以哪怕为生成每一个对象都去生产一个ScopeAlloc都没有关系(当然我们并不建议你这样做)。 对于多数的算法而言,我们不能确定它所需要的私有存储(Private GC Allocator)的内存空间是多大。或者说,通常它们也许并不大。而在仅仅申请少量内存的情形下,使用AutoFreeAlloc是不太经济的做法。 而相对的,无论算法所需的内存多少,使用ScopeAlloc都可以获得非常平稳的性能。 故此,我们的第二个结论是: AutoFreeAlloc有较强的局限性,仅仅适用于有限的场合(局部的复杂算法);而ScopeAlloc是通用型的Allocator,基本在任何情况下,你都可通过使用ScopeAlloc来进行内存管理,以获得良好的性能回报。 实现 typedef AutoFreeAllocT ScopeAlloc; typedef AutoFreeAllocT AutoFreeAlloc; ScopeAlloc.h BlockPool BlockPool的规格如下: class BlockPool{ BlockPool(int cbFreeLimit, int cbBlock); void* allocate(size_t cb); // 申请一个MemBlock void deallocate(void* p); // 释放一个MemBlock void clear(); // 清空所有申请的内存}; cbBlock cbFreeLimit class BlockPool{public: void deallocate(void* p) // 提醒:m_nFreeLimit = cbFreeLimit / cbBlock + 1 { if (m_nFree >= m_nFreeLimit) { free(p); } else { _Block* blk = (_Block*)p; blk->next = m_freeList; m_freeList = blk; ++m_nFree; } }} typedef ProxyAlloc ProxyBlockPool; template class ProxyAlloc{private: AllocT* m_alloc; public: ProxyAlloc(AllocT& alloc) : m_alloc(&alloc) {} public: void* allocate(size_t cb) { return m_alloc->allocate(cb); } void deallocate(void* p) { m_alloc->deallocate(p); } void swap(ProxyAlloc& o) { std::swap(m_alloc, o.m_alloc); }}; typedef AutoFreeAllocT ScopeAlloc; ThreadModel 时间性能分析 内存申请/释放过程 构造过程 析构过程 使用样例 class Obj{private: int m_val; public: Obj(int arg = 0) { m_val = arg; printf("construct Obj: %d\n", m_val); } ~Obj() { printf("destruct Obj: %d\n", m_val); }}; void testScope(){ std::BlockPool recycle; std::ScopeAlloc alloc(recycle); printf("\n------------------- global: have 3 objs ----------------\n"); { Obj* a1 = STD_NEW(alloc, Obj)(0); Obj* a2 = STD_NEW_ARRAY(alloc, Obj, 2); printf("------------------- child 1: have 4 objs ----------------\n"); { std::ScopeAlloc child1(alloc); Obj* o1 = STD_NEW(child1, Obj)(1); Obj* o2 = STD_NEW_ARRAY(child1, Obj, 3); printf("------------------- child 11: have 3 objs ----------------\n"); { std::ScopeAlloc* child11 = STD_NEW(child1, std::ScopeAlloc)(child1); Obj* o11 = STD_NEW(*child11, Obj)(11); Obj* o12 = STD_NEW_ARRAY(*child11, Obj, 2); } printf("------------------- leave child 11 ----------------\n"); printf("------------------- child 12: have 3 objs ----------------\n"); { std::ScopeAlloc child12(child1); Obj* o11 = STD_NEW(child12, Obj)(12); Obj* o12 = STD_NEW_ARRAY(child12, Obj, 2); } printf("------------------- leave child 12 ----------------\n"); } printf("------------------- leave child 1 ----------------\n"); printf("------------------- child 2: have 4 objs ----------------\n"); { std::ScopeAlloc child2(alloc); Obj* o1 = STD_NEW(child2, Obj)(2); Obj* o2 = STD_NEW_ARRAY(child2, Obj, 3); } printf("------------------- leave child 2 ----------------\n"); }} 我们看到,有了ScopeAlloc,内存管理就可以层层规划,成为一个内存管理树(逻辑ScopeAlloc树5)。你可以忘记释放内存(事实上你不能释放,只能clear),ScopeAlloc会记得为你做这样的琐事。这正是GC Allocator的精髓。 ScopeAlloc的名字来由,看这个样例就可以体会一二了。在《C++内存管理变革(1): GC Allocator》我们特别提到,内存管理有很强的区域性。在不同的区域(Scope),由于算法不同,而导致对Allocator需求亦不同。从总体上来讲,ScopeAlloc有更好的适应性,适合更为广泛的问题域。 Footnotes 并不是说child就是alloc的子存储(Allocator)。只是通常child生命周期比alloc短,从而有逻辑上的父子关系。 这里就很容易看出,其实alloc、child是平等的。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
6,105.469ms |