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. checkbox 删除

    先创建del.php文件: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http ...

  2. 【开源java游戏框架libgdx专题】-03-项目开发与调试

    创建libgdx项目 下载项目配置工具 gdx-setup.jar 生成项目 导入Eclipse File -> Import -> Gradle -> Gradle Project ...

  3. HTML案例练习一

    发现其实JS也是挺容易的,也挺好玩的,写的一个控制图片移动的小案例,对DOM机制还是不怎么熟. <html> <head> <style type = "tex ...

  4. Modernizr——为HTML5和CSS3而生!

    原文地址:http://www.alistapart.com/articles/taking-advantage-of-html5-and-css3-with-modernizr/ 堂主译文地址:ht ...

  5. c#利用WebClient和WebRequest获取网页源代码

    C#中一般是可以利用WebClient类和WebRequest类获取网页源代码.下面分别说明这两种方法的实现.   WebClient类获取网页源代码   WebClient类   WebClient ...

  6. Hibernate4 clob字段存取

    domain的字段: private Clob content; hibernate的xml映射 <property name="content" type="cl ...

  7. (兼容IE6)又一个提示框思密达,腾讯UED 201401242352

    找乐子 仿QQ空间的,先来看下,别嫌代码垃圾,业余菜鸟一个,用到的话就当个乐子就行了 注意: 因为有同学说需要IE6便做了一下. 已经处理了IE6,可测试. 腾讯的东西,感觉还好吧:) 使用方法老简单 ...

  8. [xfire]使用xfire开发webservice的简单示例

    目前项目上有用到xfire,所以临时看了些xfire的资料和示例,自己照着写了一个简单示例. xfire在2007年后已经停止更新,正式更名为apache cxf,也可以说是xfire2.0. xfi ...

  9. HTML&CSS基础学习笔记1.32-选择器是什么

    选择器是什么 选择器是CSS样式为了定位页面上的任意元素的一种方法. 选择器主要分为:元素标签选择器.通用选择器.类选择器.ID选择器.属性选择器.组合选择器.伪类选择器.伪元素选择器. 先做个了解, ...

  10. C语言结构体占用空间内存大小解析

    结构体的数据类型的有点我们就不啰嗦了,直接来看相同数据结构体的几种书写的格式吧. 格式一: 01.struct tagPhone 02.{ 03.     char   A; 04.     int  ...