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. Javascript 获取url参数,hash值 ,cookie

    /** * 获取请求参数 * @param key * @returns {*} */ function getRequestParameter(key){ var params = getReque ...

  2. php 写model层

    <?php /** * @author Administrator * */ class User { private $id; private $admin; private $paw; pr ...

  3. ChesFrame框架介绍

    一直以来想写一个框架,想达到的目的: 1.对曾经做过项目的总结 2.节约构建系统的成本,不用每次都从零开始做起 3.写框架并在使用中不断的完善框架,这也是个积攒过程. 经历了一个多月的时间,一个基本的 ...

  4. Mysql查看执行计划

    EXPLAIN(小写explain)显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. EXPLAIN + sql语句可以查看mysql的执行 ...

  5. JS操作DOM元素属性和方法

    Dom元素基本操作方法API,先记录下,方便以后使用. W3C DOM和JavaScript很容易混淆不清.DOM是面向HTML和XML文档的API,为文档提供了结构化表示,并定义了如何通过脚本来访 ...

  6. for update被锁定解锁

     查找被锁定的表,用户,session:SELECT object_name, machine, s.sid, s.serial#FROM gv$locked_object l, dba_object ...

  7. windows 8.1 administrator相关设置

    一.windows 8.1 开启administrator用户 windows8.1中文版,由于默认不开启administrator用户,所以需要自己手动开启 启用administrator:在cmd ...

  8. 创建 序列 存储过程 job

    掌握了 oracle中的 dbms_lock 函数,该函数 主要用于暂停执行的程序 1.用意 写job 以10分钟 为单元,前10分钟 从 1到10 插入测试表, 中间10分钟从 11到20插入测试表 ...

  9. Hive学习之一 《Hive的介绍和安装》

    一.什么是Hive Hive是建立在 Hadoop 上的数据仓库基础构架.它提供了一系列的工具,可以用来进行数据提取转化加载(ETL),这是一种可以存储.查询和分析存储在 Hadoop 中的大规模数据 ...

  10. weka打开提示内存不足的解决方法

    今天在linux中打开Weka时,打开基因数据文件的时候出现如 Not enough memory . Please load a smaller dataset or use a larger he ...