C# 与 C 、 C++ 、 D 、 Java 的性能比较
(文章出自http://mag.vchelp.net) jason
C# 性能能赶上编译型的 C/C++/D 和中间代码运行时解释的 java 吗?
微软发布了 .net 平台和 .net 平台支持的多种语言,包括 C#,vb.net, 受管 (managed extensions) 的 C++. 它们构成的体系能同时满足开发出高效的服务器架构,和开发部门对强壮的多功能的接口的需求.
和其他众多程序员一样,拿新开发语言与日常用到的语言作比较和选择的时候,我对新玩意总有莫名的偏见和怀疑.本篇文章将会讨论 .net 平台的优点,特别是相关的 C#. 我们拿 C# 和其他开发语言在高性能软件开发方面,作定量的性能比较.
因为 c# 程序编译运行分为两部分:先编译成中间代码,然后是中间代码的运行时解释执行,所以我们不仅会把他跟纯编译型的 C/C++/D 语言相比,也会把他与典型的 ( 中间代码运行时解释 ) 的 Java 语言作比较. Java 无疑是一个成熟的语言,应用在各个计算机领域. Java 程序先被编译成中间代码,然后运行时通过 java 虚拟机解释成本地机器码执行.由此看来, .net 平台,特别是为首的 c# 语言,借鉴了 java 的很多东西,包括语法和运行时支持.
对新语言(或者说时语言 + 对应的库 + 该语言的运行时支持)作完整公平的性能比较算是一个庞大考验.本文对 c# 与 C/C++/D 就开发中都涉及到的基本共通点作初步的性能比较,同时还包括各个语言所带的库的性能比较.包括:
1. 运行一次完整的程序(转载执行一个程序的时间)
2. 算法和递归(计算圆周率和 Eratorshnes's sieve )
3. 类型转换( int->float,float->int,int->string.string->int )
4. 字符串比较
5. 字符串连接
6.( 字符串特定符号统计 )
通过上面的一系列比较,将会揭示 c# 编译器效力的一些有意思的地方和 .net 运行库的性能
背景 :
虽然作比较的语言都可以算是 C 语言系列,虽然这五种语言在语法上只有或多或少的不同,但在其他方面,它们的差别是巨大的.(见表一, http://www.digitalmars.com/d/comparison.html ) . 我假设读者已经熟悉 C/C++/Java ,以及一些基本的 C# .对于 D 语言,不如其他语言那么有名,但你可以找到它的介绍
D 语言介绍
D 语言是 Walter Bright 正在开发的一种语言,安装和学习这种语言的开销很小。实质上, D 语言的目标是更成功的 C/C++ 语言,改进包括可变长度的数组,函数参数的 [in][out] 属性,自动内联,版本控制,局部函数,内建单元测试,严格的类型检查等等,包括 Java/C# 中实现的忽略指针和引用的差别 , 这在 C++ 中只有通过模板实现。它同时支持对现有 C 函数和 API 的直接支持。
它支持许多跟 Java/C# 类似的特性 , 比如垃圾收集。 D 语言跟 C#/Java 的本质区别是 D 语言运行时不需要运行时的虚拟机支持。 D 语言所支持的所有特性都是在编译连接时完成的,所以不需要运行时的额外支持。 D 语言的开发目前已经到了 Alpha 版本,将来的成熟版本在绝大多数情况下将会比 Java/C# 高效得多,除非 JIT 优化非常明显.所以我们才把 D 语言加到我们的测试当中。实际上, Walter 告诉我们, D 完全有能力产生跟 C 代码运行得一样快的代码,同时,由于自动内联,内建数组,字符串内建优化,非 /0 的字符串结束标记等特性,他相信在 D 语言正式发布的时候,它的性能将会超过 C 。
在真正的测试开始以前,我假定性能按照从高到低排序,应该是 C/C++,D,C#,Java .我跟其随后证实的其他好多人一样,一开始就怀疑 C# 产生高性能代码的能力.
性能比较
我们会在下面接着介绍的十一种不同情况下测试性能.测试平台是 2G 512M 的 pentium4 ,操作系统是 Windows XP Pro. 测试是在一套我自己设计的工具下进行的,为了符合这篇文章,我理所当然地用 C# 写了这个工具。在没有其他干扰进程的情况下,对每个测试者运行七次,分别去掉最快的一次和最慢的一次后得到的品均值,就是测试者的成绩。除了下面说到的 noop 测试,所有的测试通过高精确度定时器计算完成所有内循环所需时间完成。(译者注:比如 int->float 测量中,会通过循环运行多个 int->float 的转换,然后测量完成整个循环需要的时间)。如 Listing1 所示的代码,就是用 c# 完成的 f2i 测试。 (C 通过调用 win32 api 函数 QueryPerformanceCounter() ; C++ 通过 WinSTL 的 performance_counter;C# 通过 SynSoft.Performance.PerformanceCounter;D 通过 syncsoft.win32.perf.PerfomanceCounte ; Java 通过 System.currentTimeMillis()) 。每个程序都包含进入实质测试代码前一段 ” 热身代码 ” 来减少程序初始化 ,catch 等因素带来的不利影响。
编译器版本号如下:
C#:VC#.net v7.00.9466,.NET Framework v1.0.3705;
D: Digital Mars D compiler Alpha v0.61
Java: J2KDSE 1.4.1
C/C++ 在两个编译器下完成,分别是 Digital Mars C/C++ v8.33,STL Port v4.5 和 Inter C/C++ v7.0 ,头文件和库用 VC++ 6.0
用 Digital Mars 编译器的原因是:首先它能快速生成高质量代码,而且免费。其次是为了和 D 语言作比较,因为 D 语言用到相同的 C/C++ 连接器和很多 C 库。
为了作彻底的性能比较,所有的编译器都用了速度最优的优化方案。 Inter 编译选项是 -QaxW ,它提供两种代码路径,一种是专门为 Pentium4+SIMD 优化,一种是为其他 cpu 优化。具体的代码运行路径在运行时自动选择。 ( 通过对不同 C/C++ 编译器性能的分析,我认为 Inter 编译器针对他自己芯片的优化非常好,而对于 Digital Mars 的编译器编译出来的代码性能,可以代表普遍的 C/C++ 性能 )
对这几种语言作方方面面的比较是不太现实的,因为某些特定的功能并没有在这几种语言中都得到实现。比如 C 内建库里没有(字符串特定符号统计)函数。不过既然这篇文章的主题是讨论 C#, 所做的测试都是 C# 支持的,如果其他语言没有提供对应的功能,我们会提供一个自定义的实现。同时,如果某种语言对某种功能的内建支持非常差的话,我会考虑提供一个相对好一点的自定义支持。 ( 特别是对于 C/C++ ,至少有一两个地方看得出效率不高的内建库掩盖了语言本身的真实性能 )
简而言之,我不会列出每种测试在不同语言的具体实现代码,不过在这里,我会详细介绍下面几种测试情况 :
noop.noop 测试的是程序的装载时间 : 一个空的 main/Main 的执行时间。跟其它测试不一样,这个测试是在 ptime 工具下完成的 .( 可以从 http://synesis.com.au/r_systools.html) 获得,它提供了了 unix 环境下 time 工具类似的功能,同时它还能多次运行目标程序来获得平均值 ( 在我们的测试中, C/C++/D 运行 200 次 ,C# 运行 50 次, java 运行 30 次 ). 为了除去某些特定情况造成的影响,我们会多次测量 ( 译者注:指多次运行 ptime 程序, ptime 程序自身又会多次运行目标程序 ) ,并且取去掉最高值和最低值后的平均值。
f2i 这个测试把 1 个长度为 10,000 的双精度浮点值转换为 32bit 的整数。从 Listing 1 可以看出,使用伪随机数产生算法可以保证各语言比较相同的浮点数。这带来了两个有趣的情况,我们将在查看结果的时候予以讨论。
I2f 跟前一个相反,这个测试对针对具体某个函数进行的测试。程序生成 10000 个确定的伪随机整数,保证在不同语言中被转换的整数是相同的。
I2str 把整型转换成成字符串型。整型变量从 0 循环增加到 5000000 ,每次做一个整型到字符串型的转换 ( 见 listing2).C#/Java 用内建函数完成 ---i.ToString() 和 Integer.Tostring(i),C/D 用传统的 sprintf() 完成。 C++ 分为两次完成。第一次用 iostream 的 strstream 类,和预期的一样,这种情况下的性能非常差。为了提高效率,我采取了一些措施,所以导致了第二种方案,见 i2str2
Str2i. 把字符串转换成整型。建立一个字符串数组,内容是 i2str 转换过来的内容,然后再通过语言本身的支持或者库函数把字符串转换回去,计算转换回去的时间(见 listing3 ) .C/D 用 atoi 完成, c++ 用 iostream 的 strstream 完成, c# 用 int32.parse 完成, java 用 integer.parseint() 完成。
picalc 迭代和浮点。通过 listing 4 所示的迭代算法计算圆周率。每种语言的实现其迭代次数是 10,000,000 。