C++复兴的话题至今已被鼓吹两年有余,Herb Sutter和Bjarne Stroustrup等大牛们也为C++带来了大步伐的革新。然而,从这两年的效果而言,C++的复兴并没有发生。一方面随着世界经济的动荡,IT行业也出现了一定程度的衰退;另一方面这也是个新兴语言如雨后春笋的时代,尤其是web平台上,CoffeeScript、Dart、TypeScript等,新人阶前花更红。抛开非技术原因不谈,我更有兴趣的是,C++到底能占据多大的性能优势,以实现其复兴,尤其是在内存管理上。

Native复兴论的主要论据一:不断兴起的移动设备性能有限而且电池续航需求高,且硬件难以再现过去20年的高速发展。事实。论据二:GC比显式内存管理占用更多的内存,且在内存不足时会出现性能问题;而C++11已经基本解决内存管理安全问题,所以可以在不引入GC性能开销的条件下实现GC的好处。(注:准确地说,引用计数逻辑上也算一种GC。)

当然,C++是理论上可以做任何极限的优化的,其极限性能必然超过使用GC的语言,所以这里必须退一步,考虑一般情况。因为若要复兴,必然需要能够吸引占多数的一般应用的开发者。在移动设备上,GC确实远比桌面系统上的差,垃圾回收的开销往往很大,可以导致零点几秒的阻塞,这对游戏这样有实时性需求的应用来说,是个大问题;对非实时但有UI交互操作的应用,也会影响界面响应的平滑度。为了减少回收的开销,又必然占用更多的内存以便延迟回收减少阻塞,而占用更多的内存也可能导致更高的cache miss率。这样,一个使用GC的语言,往往要使用3倍或更多的内存,而又面对内存并不丰富的移动设备。这看起来确实是C++能完胜的地方。

而事实上,市场上并不乏使用Java、C#乃至JavaScript、Lua开发的移动设备实时应用。它们绕过GC性能问题的方法也和C++一样,做显式内存管理。用GC的语言做显式内存管理听起来有点怪,但其实多数也是C++里常用的方法,比如启动时预先分配对象内存,利用数组预留内存、实现复杂数据结构(当年写过Basic程序的人应该没少做这个)等,以便减少运行时动态内存分配。唯一做不了的就是C/C++的自定义内存分配器。事实上,在游戏领域,自定义内存管理是很普遍的事,C++的堆分配开销相对实时需求往往还是有些偏大,而且还有内存碎片问题,在后期优化阶段,多数会被替换成预分配的大块内存。

因此,我倾向于认为C++有优势但对一般应用而言并非有绝对优势,C++的优势领域和以前相比并没有太大的不同。对于GC的语言,在必要时,也是可以做显式内存管理的。

附一、C++11的unque_ptr、shared_ptr性能讨论:使用这些智能指针对象并非没有GC开销。首先,对象的析构函数调用要引发成员智能指针的析构,对于大的对象结构,这相当于一次树的遍历。其次,unque_ptr、shared_ptr是线程安全的,这是一个非常好的特性的同时,也是需要一定的实现代价的。尽管它们是用远比锁高效的原子操作实现的,但原子操作仍然意味着不能缓存在寄存器,而且写操作时会flush cache(数百时钟周期的开销),所以它们应被用来管理对象的ownership,而对不涉及ownership的参数传递等,直接用简单的对象指针就好。

类似的,Windows上COM对象指针的传递,按规则,所有的参数、返回值传递都要加减引用计数。这个尽管并非使用原子操作、并非线程安全,仍然导致很多冗余的引用计数操作。所以D3D10开始使用了非标准的COM用法,以减少不必要的引用计数。

附二、GC语言上连续内存分配的讨论:说到预分配大块连续内存,通常会最先想到struct array。这个.NET里还有的用,但Java、JavaScript、Lua等就不支持了。用class也能做到预分配内存,但不是连续空间,cache miss率明显大于struct。尽管如此,它们都支持primitive类型的连续内存数组,而且primitive的数组才是性能最佳的数据结构。也就是说,内存不按对象分配而按属性分配,使用position = new float[n * 3], velocity = new float[n],而不是class Bullet { float3 position; float velocity; } bullets = new Bullet[n]。这样各个属性值的内存布局更加紧凑,由于一般一个函数只会访问对象的少数属性,这样紧凑的布局会大幅提高cache的命中率。当然,也不是说非得用primitive不可,比如.NET用struct的话,可以让position变成struct float3的数组,更易读易用一些。

习惯于教科书式OO的人对这么设计数据结构可能会感到不舒服,因为这似乎破坏了OO。但我认为,这只是实现细节,并不影响外部把它封装成对象的集合。也可以换个角度看,这只是另一种OO的设计,只不过是以属性集合作为对象而已。类似方案也早就出现在Ogre 2.0草案里,以缩小其和商用图形引擎的性能差距。

最后还要强调一下,这毕竟是在“fight the language”,并不是个简单的日常使用的设计,切莫过度使用。在性能可接受的条件下,可维护性优先。

附三、GC的性能特征:GC的性能特征随GC的类型不同而不同。如今主流多是Mark and Copy类的,其特点是对生命期超长(比如从程序启动到退出)的对象和生命期超短(比如仅限一个函数调用内部)的对象最高效,几乎没什么开销。尽量避免finalizer,有finalizer的对象的回收代价很大,必须要用的,要用Dispose等显式释放。回收时堆扫描的性能和对象数量相关,就是说对中、长生命期的对象而言,少量大数组对象远比大量小对象高效。

GC与显式内存管理的更多相关文章

  1. 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配

    垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  2. 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法

    垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  3. 垃圾回收GC:.Net自己主动内存管理 上(三)终结器

    垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主 ...

  4. [转载]linux段页式内存管理技术

    原始博客地址: http://blog.csdn.net/qq_26626709/article/details/52742470 一.概述 1.虚拟地址空间 内存是通过指针寻址的,因而CPU的字长决 ...

  5. 【Linux】浅谈段页式内存管理

    让我们来回顾一下历史,在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址.如果这个系统只运行一个程序,那么只要这个程序所需的内存不要超过该机器的物理内存 ...

  6. linux 段页式内存管理

    http://blog.chinaunix.net/uid-24227137-id-3723898.html 页是信息的物理单位,分页是为了实现离散分配方式,以消减内存的外零头,提高内存的利用率从:或 ...

  7. GC(一)内存管理与垃圾回收

    参考文章: 内存分配.GC原理与垃圾收集器:http://www.importnew.com/23035.html g1垃圾回收器:http://blog.jobbole.com/109170/ cm ...

  8. WINDOWS页式内存管理解析

    jpg 改 rar

  9. linux 内核源代码情景分析——i386 的页式内存管理机制

    可以看出,在页面目录中共有210 = 1024个目录项,每个目录项指向一个页面表,而在每个页面表中又共有1024个页面描述项. 由图看出来,从线性地址到物理地址的映射过程为: 1)从CR3取得页面目录 ...

随机推荐

  1. C#解leetcode 152. Maximum Product Subarray

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  2. DWZ框架学习一

    测试DWZ框架弹出框设置成模态 刚刚上手DWZ框架,感觉灰常好用,对于我这种特别懒的人来说,真的是拖拽编程 看了下官方的视频讲解,自己试着做了一个小测试,里面的组件什么的都不用写,直接拿来用 这里附上 ...

  3. 使用Javascript限制文本框只允许输入数字

    很多时候需要用到限制文本框的数字输入,试过许多方法,都不太理想,遂决定自己实现一个来玩玩.曾经使用过的方法通过onkeydown事件来控制只允许数字: <input onkeydown=&quo ...

  4. Java枚举类型理解

    Enum格式理解 Enum的格式可以看做跟class关键字一样 class的定义格式如下: public class abc{ } enum的定义格式如下: Public enum abc { } 引 ...

  5. 关于uploadify 没有显示进度条!!!!

    如果你也弄了很久不知道为什么不出现上传进度条!,那就一定要看这里了! 我注释了 queueID 属性后 就出现了!!!!! 就是这么简答! //添加界面的附件管理 $('#file_upload'). ...

  6. Linux sed命令在指定行前后添加内容

    一.在匹配行前后加内容在包含www.baidu.com的行前面或后面添加多一行内容www.qq.com#匹配行前加sed -i '/www.baidu.com/i www.qq.com' domain ...

  7. 最全ASCLL码

    结果 描述 实体编号   space ! exclamation mark ! " quotation mark " # number sign # $ dollar sign $ ...

  8. [个人原创]关于java中对象排序的一些探讨(三)

    这篇文章由十八子将原创,转载请注明,并标明博客地址:http://www.cnblogs.com/shibazijiang/ 对对象排序也可以使用Guava中的Ordering类. 构造Orderin ...

  9. C++ Built-In Array 的语义

    C++ 编译花了大量精力使得class和原始类(primitive types)的用法一致.比如array的应用: A a[100]:// A is class int b[100]: 虽然a是用户定 ...

  10. JQuery相关的网络资源

    jquery插件列表 国外网站:http://plugins.jquery.com/ 国内网站:http://www.oschina.net/project/tag/273/jquery