C++ 0x 里的垃圾收集器

2008-03-28 07:31:58来源: 阅读 ()

新老客户大回馈,云服务器低至5折

好像最近C++标准地下工会在公司附近开会,所以我们上班时偶尔可以看到工会成员矫健的身影。我们也近水楼台,得以听到关于C++0x进展的一些科普报告。上次Bjarne Stroustrup做了关于泛型编程的讲座后,Symantec实验室的Michael Spertus也做了一个关于C++ 0x里垃圾收集的讲座。Michael Spertus当年写出了IBM PC上最早的C编译器,也是C++ 0x里垃圾收集器的主要倡议人。
 
    Michael从什么是GC开始讲起。三言两语后,谈到在C++ 0x里加入GC的动机。一是许多数据结构,对象,或者资源的生命期难以事先确定、静态管理。我们需要某种形式的动态管理技术。很多老大以为C++高手的标志之一是不需要GC。如果这样想,Unreal的Tim Sweeney就笑了。Unreal引擎里就大量使用GC。如果一个系统需要管理大量对象,要求高吞吐量,但可以容忍偶尔的系统延迟的话,GC是颇为不错的工具。所以3D/建模,2D图像处理等计算都可以用到GC。第二个理由非常有煽动力:帮助C++程序清除内存泄露。我们不必在C++里排斥人肉内存管理。问题是,人肉内存管理难以尽善尽美。大型系统里内存泄露几乎不可避免。每次泄露也不多,10来KB到几个MB。但积累起来,也就造成诸多问题。因为每次泄露量不大,也不像Java等基于GC的语言,“泄露”随时都在发生。所以可以通过定期执行GC来清除这些泄露,也不用消耗过多资源。Michael后来举了一个颇有说服力的例子:某电信公司的交换机,100多万行程序,有持续的内存泄露。每小时必须重启一次。使用GC后,内存泄露消失,交换机不用再定时重启。重启时系统堆上有大概200个线程,500MB内存。这样算来,每线程每小时泄露2.5M。每线程每分钟不过42KB。而每分钟收集42KB内存对系统根本不会有什么影响。最后实测下来,收集500MB不过需要两秒种。分摊到一个小时内,完全可以忽略不计。这样的GC应该叫LC—Litter Collector。还有一个例子是Michael向Mozilla浏览器里注入GC。每次用户操作使得GC回收大概10KB的内存,实在是小菜一碟。
 
    Michael接着谈到C++ GC的发展。C++ GC已经成熟,可以标准化了。系统研究的老大们已经做了20年的研究。GC也用到了形形色色的C++系统里。标准化的关键是C++的GC是可选项:GC可以被关掉,而且程序员能够在代码里随时决定用GC还是人肉内存管理。这样才能满足某些系统实时回收资源的要求。而且已有的标准库就不需要重新编译。
 
    Michael比较了shared-ptr和GC。GC的主要缺陷是不能即时回收内存,而强项除了方便程序员外,就是性能了。GC的性能(尤其是分摊后的性能)在多方面的基准测试里超过自动指针管理已经不是新闻。Michael举了Boost线程安全基准测试的数据。GC比用share-ptr的测试程序快10倍以上。比Java的hotspot程序或者Gcj编译出的程序快5倍以上。甚至用人肉内存管理的C程序也比GC慢,不过比Boost的程序员快3倍左右。
 
    既然GC在C++里用了很多年了,干嘛还要标准化呢?主要有三条原因。一是内置的GC才能读取C++的类型系统。高质量的GC需要知道数据的类型信息。没有类型信息,GC变得非常保守,需要经常扫描大片内存来决定是否能回收某个对象。这样让GC变得很慢。第二个原因是我们需要防止编译优化对GC造成破坏。如果不标准化,标准的C++编译器不可能知道第三方的GC需要什么样的优化。第三是为了允许不同的厂家能共享GC管理的对象。不然微软在某个控件里用了GC,怎么能保证同样的控件到了Borland的系统里也正常回收内存呢?
 
    目前的C++ 0x的提议走的是稳健路线。任何功能都要经过实际应用的检验。GC用于特殊场合(比如前面的LC)。通过句法糖的方法实现,也就是说没有新的关键词。API的数目要尽可能小。当然了,C++的类型系统不如Java那么强,所以GC也相应复杂一些。标准提案里包涵了好几种GC类型:
  • gc_required: 所有资源都要通过GC收集。由gc_required控制的区域内所有指针都是经过标注的gc指针。
  • gc_forbidden: 不允许GC。必须人肉回收内存。由gc_forbidden控制的代码区域里只有原始指针。
  • gc_safe:缺省情况。忽略人肉管理的内存,但处理没有被手工回收的部分。
  • gc_strict: GC处理的代码段里整型变量不包含指针(比如不能在DWORD里放指针)。
  • gc_relax: GC处理的代码段里整型变量可以包含指针。这种情况下GC往往需要扫描大量内存来确保安全回收内存。
  • gc_cast: 把一种类型的GC转换成另外一种类型。
下面是个例子:
 
        class A {
            A *next;
            B b; // 这里不能用gc_strict, 因为我们不知道B里的数据信息。
            gc_strict int data[10000]; // 我们肯定data数组里没有指针
        }
 
    同时C++标准库里还提供两个函数:bool std::is_garbage_collected(),和class std::gc_lock()。一个用来检查某块内存是否被GC回收,另外一块用来设定所谓的GC临界区。临界区内的内存不能被回收,这样可以防止GC不合时宜地启动。
 
    呵呵,够复杂吧?引入这么多操作的原因之一是要和老的C++系统互动。比如说下面一些情况:
  • 两个DLL,一个用了全局GC,另外一个没有怎么办?如果每个DLL都链接到自己的C类库怎么办?
  • 如果一个共享库希望在内部使用GC,但不影响堆的其它部分怎么办?
  • 如果我们在C++程序内调用libjvm怎么办?libjvm装载后,C++程序可以读取到载入的JVM里的堆,未必我们需要对已经有Java GC控制的内存再做垃圾处理?
    针对不同的情况,我们得祭出不同的GC。嘿嘿,C++程序员们,准备好你们的钱包哈。如果2009年后C++ 0x开始流行,要买的书就多了。比如Effective C++ GC, Master C++ GC, C++ GC with Legacy Applications,  Modern C++ Design with GC, Expert C++ with GC, Expert C++ without GC。这个世道,不容易啊。
  
    那finalization嗫?没有了finalization的GC还叫GC么?C++的大仙们当然不会漏掉这么重要的环节。只不过因为finalization本身就很复杂了(比如说回收内存时应该怎样调用回收对象的析构函数?如果回收时立刻调用,会造成线程不安全),所以有另外的提案讨论乐。

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:C++的进化:C++0x一瞥

下一篇:C++0x设计之路