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. Python开发【第七篇】:面向对象 和 python面向对象进阶篇(下)

    Python开发[第七篇]:面向对象   详见:<Python之路[第五篇]:面向对象及相关> python 面向对象(进阶篇)   上一篇<Python 面向对象(初级篇)> ...

  2. 简单html以及css的用法

    我将利用三天的时间来完成制作京东首页的静态页面效果,其中包含的内容有html以及css. 1.在开发进行之前,首先要配置开发环境:我们需要安装sublime  webstorm  vscode  Hb ...

  3. urlwrite伪静态(SAE、PHP、JSP)

    在SAE里,直接配置config.yaml文件,文件可以配置的内容包含: 目录默认页面 自定义错误页面 压缩 页面重定向 页面过期 设置响应Header的Content-Type appname: x ...

  4. 注册表修改IP地址和DNS等信息

    ---------------------win8系统 1. 2. 3. --------------------------------------------------------------- ...

  5. Linux下解决高并发socket最大连接数所受的各种限制(解除IO限制)

    linux作为服务器系统,当运行高并发TCP程序时,通常会出现连接建立到一定个数后不能再建立连接的情况 本人在工作时,测试高并发tcp程序(GPS服务器端程序),多次测试,发现每次连接建立到3800左 ...

  6. Deep Learning学习随记(一)稀疏自编码器

    最近开始看Deep Learning,随手记点,方便以后查看. 主要参考资料是Stanford 教授 Andrew Ng 的 Deep Learning 教程讲义:http://deeplearnin ...

  7. cxf客户端代码设置设置访问用户名、密码、证书域名不匹配认证通过

    最近和第三方联调,需要调用对方的wsdl,但是调用必须的设置用户名.密码验证.在soapUI里面设置用户名.密码调用通过.但是怎么转换成JAVA代码呢,搜索了好多解决方案,现将代码截图如下: 1.SO ...

  8. Python django admin 替换表单控件

    测试版本: Python 2.7 Django 1.6.2 models.py from django.db import models class Article(models.Model): ti ...

  9. js循环便利json数据

    var data=[{name:"a",age:12},{name:"b",age:11},{name:"c",age:13},{name: ...

  10. unset() isset() empty difined()操作变量详解

    isset()函数 一般用来检测变量是否设置 格式:bool isset ( mixed var [, mixed var [, ...]] )  功能:检测变量是否设置 返回值:  若变量不存在则返 ...