TAT.Kinvix Javascript,C++,C#,Java,Lua,Python,Ruby,F#语言渲染性能评测
In 未分类 on 2012年06月08日 by view: 15,270
3


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),但对本人而言,此次实验加深了对各种语言性能的了解,或应该是消除了一些误解。简单总括运行性能方面的体验和感想:

  1. C++和 VM 类静态语言可以大约只差 2~4 倍,JVM 和 CLR 差异不大。
  2. C++和动态语言之比,则可以是 15~5000 倍,不同动态语言的差异很大。
  3. 一直以为 Lua(JIT) 会是最快的通用脚本语言,没想到此测试中败给 JavaScript(V8),或许应该多点研究嵌入 V8 引擎 (SWIG 能支持就最理想了)。
  4. 以为 Python 和 Ruby 的性能相差不远,但测试结果两者大相径庭。暂时不太了解 Ruby 的特长,或许之后再研究其优点是否能盖过其性能问题。

最后建议读者,若要为某应用挑选语言,又要顾及性能,那么应该自己做实验去比较。不要盲目相信一些流言或评测 (包括本文)。

更新

出处:Milo 的游戏开发

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2012/06/javascriptc-speed/

  1. 寒轩 2014 年 7 月 16 日

    这个 LuaJIT 的版本有点低,换现在最新的 JIT 2.0.3 试试

  2. 流泪的鳄鱼 2012 年 8 月 28 日

    能做个 java7 的吗?

  3. html6game 2012 年 7 月 24 日

    虽然偶米做过这些测试,也不会这么多语言,但平常在调试脚本的时候,有时候也调试部分程序,像 c#和 vc,感觉区别不大,现在大部分人都用谷歌浏览器,而且用的 win7,感觉现在测试语言性能意义不大,因为同一图形或过渡,表现上相差非常小,实际上大部分网页中的应用不需要那么高的要求,能够运行并且不卡,就可以了,主要还是考虑的脚本兼容方面的。最后还想说,java 并不是因为 jdk 快,而是它的算法太超前,特别是在图表应用方面,c++都米它跑得快。而现在比较流行的 flash 和 js 图表,其速度顶多就 java 图表的 1/10。可能偶很多地方局限了,但偶都是自己实践才得出这点理论。

发表评论到 寒轩