512x512 像素,每像素 10000 个采样,Intel C++ OpenMP 版本渲染时间为 18 分 36 秒。估计 Ruby 版本約需 351 天。
本人陆续移植了 C++代码至 Java、JavaScript、Lua、Python 和 Ruby,赵姐夫亦尝试了 F#。本文提供测试源代码、测试结果、简单分析、以及个人体会。
声明
首先,为免误会,再次重申,本测试有其局限,只能测试某一应用、某一实现的结果,并不能反映编程语言及其运行时的综合性能,亦无意尝试这样做。而实验环境也只限于某机器、某操作系统上,并不全面。而且,本测试只提供运行时间的结果,不考虑、不比较语言/平台间的技术性和非技术性优缺点,也没有测试运行期内存。世界上的软件应用林林总总,性能需求也完全不同,本测试只供参考。
由于本人第一次使用 Python 和 Ruby,若代码有不当之处,敬请告之。当然也非常乐见其他意见。
测试内容
本文测试程序为一个全局光照渲染器,是一个 CPU 运算密集的控制台应用程序 (console application),功能详见前文。在前文刊出后,本人进行了一点 profiling、优化,并把代码重新格式化。本渲染器除了有大量数学运算,亦会产生大量临时对象,并进行极多的方法调用 (非虚函数)。本测试有别于人工合成的测试 (synthetic tests,例如个别测试运算、字串操作、输入输出等),是一个有实际用途的程序。
移植时尽量维持原代码的逻辑,主要采用面向对象范式。优化方面,不进行人手内联函数 (inline function),但优化了一些不必要的重复运算。
测试配置
- 硬件: Intel Core i7 920@2.67Ghz(4 core, HyperThread), 12GB RAM
- 操作系统: Microsoft Windows 7 64-bit
测试名称 | 编译器/解译器 | 编译/运行选项 |
VC++ | Visual C++ 2008 (32-bit) | /Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /Gy /arch:SSE /fp:fast |
VC++_OpenMP | Visual C++ 2008 (32-bit) | /Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /Gy /arch:SSE /fp:fast /openmp |
IC++ | Intel C++ Compiler (32-bit) | /Ox /Og /Ob2 /Oi /Ot /Qipo /GA /MD /GS- /Gy /arch:SSE2 /fp:fast /Zi /QxHost |
IC++_OpenMP | Intel C++ Compiler (32-bit) | /Ox /Og /Ob2 /Oi /Ot /Qipo /GA /MD /GS- /Gy /arch:SSE2 /fp:fast /Zi /QxHost /Qopenmp |
GCC | GCC 4.3.4 in Cygwin (32-bit) | -O3 -march=native -ffast-math |
GCC_OpenMP | GCC 4.3.4 in Cygwin (32-bit) | -O3 -march=native -ffast-math -fopenmp |
C++/CLI | Visual C++ 2008 (32-bit), .Net Framework 3.5 | /Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /fp:fast /Zi /clr /TP |
C++/CLI_OpenMP | Visual C++ 2008 (32-bit), .Net Framework 3.5 | /Ox /Ob2 /Oi /Ot /GL /FD /MD /GS- /fp:fast /Zi /clr /TP /openmp |
C# | Visual C# 2008 (32-bit), .Net Framework 3.5 | |
*C#_outref | Visual C# 2008 (32-bit), .Net Framework 3.5 | |
F# | F# 2.0 (32-bit), .Net Framework 3.5 | |
Java | Java SE 1.6.0_17 | -server |
JsChrome | Chrome 5.0.375.86 | |
JsFirefox | Firefox 3.6 | |
LuaJIT | LuaJIT 2.0.0-beta4 (32-bit) | |
Lua | LuaJIT (32-bit) | -joff |
Python | Python 3.1.2 (32-bit) | |
*IronPython | IronPython 2.6 for .Net 4 | |
*Jython | Jython 2.5.1 | |
Ruby | Ruby 1.9.1p378 |
* 见本文最后的"7. 更新"一节
渲染的解像度为 256x256,每象素作 100 次采样。
结果及分析
下表中预设的相对时间以最快的单线程测试 (IC++) 作基准,用鼠标按列可改变基准。由于 Ruby 运行时间太长,只每象素作 4 次采样,把时间乘上 25。另外,因为各测试的渲染时间相差很远,所以用了两个棒形图去显示数据,分别显示时间少于 4000 秒和少于 60 秒的测试 (Ruby 是 4000 秒以外,不予显示)。
Test | Time(sec) | Relative time |
IC++_OpenMP | 2.861 | 0.19x |
VC++_OpenMP | 3.140 | 0.21x |
GCC_OpenMP | 3.359 | 0.23x |
C++/CLI_OpenMP | 5.147 | 0.35x |
IC++ | 14.761 | 1.00x |
VC++ | 17.632 | 1.19x |
GCC | 19.500 | 1.32x |
C++/CLI | 27.634 | 1.87x |
Java | 30.527 | 2.07x |
C#_outref | 44.220 | 3.00x |
F# | 47.172 | 3.20x |
C# | 48.194 | 3.26x |
JsChrome | 237.880 | 16.12x |
LuaJIT | 829.777 | 56.21x |
Lua | 1,227.656 | 83.17x |
IronPython | 2,921.573 | 197.93x |
JsFirefox | 3,588.778 | 243.13x |
Python | 3,920.556 | 265.60x |
Jython | 6,211.550 | 420.81x |
Ruby | 77,859.653 | 5,274.69x |
C++/.Net/Java 组别
静态语言和动态语言在此测试下的性能不在同一数量级。先比较静态语言。
C++和.Net 的测试结果和上一篇博文相若,而 C#和 F#无显著区别。但是,C++/CLI 虽然同样产生 IL,于括管的.Net 平台上执行,其渲染时间却只是 C#/F#的 55% 左右。为什么呢?使用 ildasm 去反汇编 C++/CLI 和 C#的可执行文件后,可以发现,程序的热点函数 Sphere.Intersect() 在两个版本中,C++/CLI 版本的代码大小 (code size) 为 201 字节, C#则为 125 字节! C++/CLI 版本在编译时,已把函数内所有 Vec 类的方法调用全部内联,而 C#版本则使用 callvirt 调用 Vec 的方法。估计 JIT 没有把这函数进行内联,做成这个性能差异。另外,C++/CLI 版本使用了值类型,并使用指针 (代码中为引用) 作参数传送。若把 C#的版本的 Vec 方法改写为:
1
2
3
4
5
6
7
8
9
|
//class Vec //{ //public static Vec operator +(Vec a, Vec b) //} struct Vec { void Add( ref Vec a, ref Vec b, out Vec c); } |
那么,struct 不用 GC,同时 ref/out 不用复制,其性能会比较高。但是代码会变得很难看:
1
2
3
4
5
6
7
|
// 原来用运算符重载 (operator overloading): a = b * c + d; // 改用 ref/out Vec e; Vec.Mul( ref b, ref , c, out e); Vec.Add( ref e, ref d, out a); |
为了维持让语言"正常"的使用方法,本实验不采用这种 API 风格 (更新: 加入了 C#_outref 测试,詳見文末)。
然而,托管代码 (C++/CLI) 的渲染时间,仅为原生非括管代码 (IC++) 的 1.91 倍,个人觉得.Net 的 JIT 已经非常不错。
另一方面,Java 的性能表现非常突出,只比 C++/CLI 稍慢一点,Java 版本的渲染时间为 C#/F#的 65% 左右。以前一直认为,C#不少设计会使其性能高于 Java,例如 C#的方法预设为非虚,Java 则预设为虚;又例如 C#支持 struct 作值类型 (value type),Java 则只有 class 引用类型 (reference type),后者必须使用 GC。但是,这个测试显示,Java VM 应该在 JIT 中做了大量优化,估计也应用了内联,才能使其性能逼近 C++/CLI。
纯 C++方面,Intel C++编译器最快,Visual C++慢一点点 (1.19x),GCC 再慢一点点 (1.32x)。这结果符合本人预期。 Intel C++的 OpenMP 版本和单线程比较,达 5.16 加速比 (speedup),对于 4 核 Hyper Threading 来说算是不错的结果。读者若有兴趣,也可以自行测试 C# 4.0 的并行新特性。
动态语言组别
首先,要说一句,Google 太强了,难以想像 JsChome 的渲染时间仅是 IC++的 16.12 倍,C#的 4.94 倍。我有信心用 JavaScript 继续写图形、物理方面的博文了。
以下比较各动态语言的相对时间,以 JsChrome 为基准。 Chrome 的 V8 JavaScript 引擎 (1.00x) 大幅抛离 Firefox 的 SpiderMonkey 引擎 (15.09x)。而 LuaJIT(3.49x) 和 Lua(5.16x) 则排第二和第三名。 Lua 的 JIT 版本是没有 JIT 的 68%,并没有想像中的快,但是也比 Python(16.48x) 快得多。曾听说过 Ruby 有效能问题,没想到问题竟然如此严重 (327.31x),其渲染时间差不多是 Python 的 20 倍。
我认为,本实验中,不同语言的性能差异,并非在于数值运算,而是对象生成及函数调用。我使用 Python 内建的 profiling 功能:
1
|
python -m profile smallpt.py |
从结果发现,Vec 类共产生约 15 亿个实例,Vec 的方法调用约 17.5 亿次,intersect() 共调用 5.7 亿次,产生随机数 5.7 亿个,radiance() 调用 (即追踪的路径线段)6.5 百万次。这些庞大数字,放大了对象生成和函数调用的常数开销 (overhead)。
结语
也许本博文的意义不大 (yet-another-unfair-biased-performance-comparison-among-programming-languages),但对本人而言,此次实验加深了对各种语言性能的了解,或应该是消除了一些误解。简单总括运行性能方面的体验和感想:
- C++和 VM 类静态语言可以大约只差 2~4 倍,JVM 和 CLR 差异不大。
- C++和动态语言之比,则可以是 15~5000 倍,不同动态语言的差异很大。
- 一直以为 Lua(JIT) 会是最快的通用脚本语言,没想到此测试中败给 JavaScript(V8),或许应该多点研究嵌入 V8 引擎 (SWIG 能支持就最理想了)。
- 以为 Python 和 Ruby 的性能相差不远,但测试结果两者大相径庭。暂时不太了解 Ruby 的特长,或许之后再研究其优点是否能盖过其性能问题。
最后建议读者,若要为某应用挑选语言,又要顾及性能,那么应该自己做实验去比较。不要盲目相信一些流言或评测 (包括本文)。
更新
- 2010/7/7: 新增的 C#_outref 测试,按 noremorse 的建议,把 Vec 和 Ray 变作 struct,所有函数传送这两种对象改为 ref/ out。 源代码。
- 2010/7/8: 新增 IronPython 和 Jython。
- 2010/7/8: 园友猫粮撰文 《AS3 的光线跟踪极限测试》,看来 AS3 性能不太好。
- 2010/7/10: 园友 Domslab 撰文《对《C++/C#/F#/Java /JS/Lua/Python/Ruby 渲染比试》一文的补充——增加 Mono 测试》,比较了 gcc/mono C#/Java 在 Windows/Linux 的性能。
- 2010/7/11: 园友 noremorse 撰文 《Swifter C#之 inline 还是不 inline,这是个问题》,以本例研究.Net Runtime 的内联机制。
出处:Milo 的游戏开发
寒轩 2014 年 7 月 16 日
这个 LuaJIT 的版本有点低,换现在最新的 JIT 2.0.3 试试
流泪的鳄鱼 2012 年 8 月 28 日
能做个 java7 的吗?
html6game 2012 年 7 月 24 日
虽然偶米做过这些测试,也不会这么多语言,但平常在调试脚本的时候,有时候也调试部分程序,像 c#和 vc,感觉区别不大,现在大部分人都用谷歌浏览器,而且用的 win7,感觉现在测试语言性能意义不大,因为同一图形或过渡,表现上相差非常小,实际上大部分网页中的应用不需要那么高的要求,能够运行并且不卡,就可以了,主要还是考虑的脚本兼容方面的。最后还想说,java 并不是因为 jdk 快,而是它的算法太超前,特别是在图表应用方面,c++都米它跑得快。而现在比较流行的 flash 和 js 图表,其速度顶多就 java 图表的 1/10。可能偶很多地方局限了,但偶都是自己实践才得出这点理论。