游戏开发论坛

 找回密码
 立即注册
搜索
楼主: wingser

选择C#的理由

[复制链接]

28

主题

685

帖子

703

积分

高级会员

Rank: 4

积分
703
发表于 2004-11-9 09:59:00 | 显示全部楼层

再贴:http://mag.vchelp.net/200312/fanyi.htm

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 。



28

主题

685

帖子

703

积分

高级会员

Rank: 4

积分
703
发表于 2004-11-9 09:59:00 | 显示全部楼层

Re:选择C#的理由

Picalcr. 递归。通过 listing5 所示的算法计算圆周率。对每种语言,我们会做 10000 次运算,考虑到 java 堆栈在深层地归时很容易被耗尽,所以每次运算限制在 4500 次递归以内。


Sieve. 叠代。通过 Listing 6 所示的迭代算法( Eratosthenes's sieve ( http://www.math.utah.edu/~alfeld/Eratorthenes.html ))得到质数。每种语言的实现其迭代次数是 10,000,000 。




Strcat. 字符串连接 --- 通常是大量运行时开销的根源,也是导致程序低效的潜在领域。(我以前做过一个对性能敏感的项目,通过改善效率底下的字符串连接,我获得了 10% 的整体性能提升) . 测试通过构造四种不同类型的变量 :short,int, double 和一个 string ,然后把它们连接成一个 string 变量完成,连接时添加用分号分割.在连接的时候采用四种不同的连接方

法 :1) 默认连接方法 2) 故意构造出来的导致效率低下的连接方法 3)C 语言种典型的 printf/Format 格式,需要重新分配空间 4) 为提高性能而进行的硬编码 . 代码摘录见 Listing7





  

Strswtch. 通过级联 switch 进行字符串比较 . 通过语言支持的 switch 语句进行比较或者通过一些列的 if-else 语句模拟( Listing 8 ).这项测试是为了检验 c# 的字符串处理功能是否如他所声称的那样高效.测试分成两部分 : 首先是进行字符串之间的赋值,也就是同一身份的比较(译者注:也就是字符串内部的指针直接复制,不涉及到字符串内容的拷贝).其次是通过字符串的拷贝后进行比较,这样就避免了同一身份比较而是进行进行字符串之间的字面比较.每种语言上都进行 850000 次叠代,因为超过这个次数后会导致 java vm 内存耗尽.


strtok 字符串分段。字符串的运行效率也极低,有时甚至引发严重的运行开销。这个测试( Listing 9)读取一个文本文件的全部内容至一个字符串,然后拆分成段。第一个变量用字符分隔符:‘;'分隔字符串,并忽略所有的空白。第二个变量也用分号分隔,但保留了空白。第三个和第四个变量用字符串分隔符:<->,并相应的忽略和保留了空白处。C里仅第二个变量用了strtok(),C++四个变量都用STLSoft的string_tokeniser模板,但各自正确给参数赋值。C#第一个变量用System.String.Split(),2至4变量用SynSoft.Text.Tokeniser.Tokenise();Java用java.util.StringTokenizer,仅支持变量2和4(注:我选用STLSoft的string_tokeniser模板不是我要为STLSoft作宣传,而是测试表明它比Boost的快2.5倍,而Boost是大多数开发人员在查找库时的首选)。


结果

在 noop测试中,表2列出了以微秒(us)为单位装载一个空操作的平均开销。显然C,C++,D的差别不合逻辑,每一个大约是5-6毫秒,而Intel确低至2ms。我所想到的唯一可能是这个编译器优化了所有的C实时运行库的初始化工作。如果是这样,这个性能在其他非试验性即实际应用中不能体现出来,因为它至少需要一些C … ,这有待深入研究。





C#用了大约70ms,Java约0.2秒。C#和Java的装载清晰的表明其实时开销在于它们的实时运行的结构基础(VM和支持DLLs)。但是除了大规模的命令行的商业化批处理,对极大型系统(上百万条代码)或CGI的基础构建,启动开销在大多数情况下不重要。重荷服务器允许在数秒内启动,所以语言间200ms的差异就微乎其微了。


其余的结果分三部分显示。图 2和3分别展示了字符串连接和分段的结果。图1包含了其余全部的测试结果,我们现在先来看看它。








图 1中,对各测试项,C,C++,D,Java的结果均以其相应测试项C#运行时间的百分比显示。因此,在100%线上下波动的结果说明与C#性能相当。高百分比说明C#性能较优,而低百分比表明C#性能相对较差。

28

主题

685

帖子

703

积分

高级会员

Rank: 4

积分
703
发表于 2004-11-9 09:59:00 | 显示全部楼层

Re:选择C#的理由

f2i 抛开Intel不谈,可以看出C#在浮点到整型的转换中效率远远高于其余四个,仅是C,C++, 和D开销的2/3,是Java的2/5。在我让它们使用相同的随机数发生器前,不同语言的性能差异极大,这说明转换速度非常依赖具体的浮点数值,当然这是后见之明了。为了确保每个语言做相同的转换,程序计算转换值的总和,这也有助于任何过度的优化去除整个循环。可以得知这种转化在语言间正确度不同。每种语言的计算总和迭代10,000次的结果在表3中列处。它们表明(虽然无法证实)C,C++,和D是最正确的,Java其次,C#最差。C#牺牲正确性和降低处理器浮点精度(参见 引用)换取优越性能是令人信服的,但事实上做此判断前我还需要更多的证据。至于Intel,说它的性能没有什么更卓越的也是公平的。




5个语言这里效率相当。另4种性能比C#低5%以内。唯一叫人感兴趣的是这四个语言都是性能比C#略好。这表明C#确实效率低,虽然在这方面只低一点。这与从整型到浮点型转换的结果相反。同样,值得我们注意的是,虽然Java在浮点到整型的转换中性能很低,但在整型到浮点的转换中却是最好的,比C#高10%。对f2i,Intel表现极佳,其C和C++分别只用了C#时间的10%和23%。

i2str 整型到字符型的转换。 C#比C和D的printf略好。比C++的以iostream为基础的转换效率要高。它是C#运行时间的4倍(对Intel 和VC6 STL/CRT是5倍)。但Java性能出色,用了C#2/5的时间。因为C++的效率低下,第二个变量使用STLSoft的 integer_to_string<> 模板函数。它在很多编译器上比C和C++库的所有可利用的转换机制要快。在这种情况下,性能比C#高10%,比C和D高约20%。但是因为Intel编译器的优化影响, integer_to_string<> 似乎找到了其完美搭档:其时间比C#低30%,是第三个速度超过Java的,比Iostream快17倍以上。认为它很可能已经接近了转换效率的上限也是合理的。

str2i 字符型到整型的转化结果与前面大相径庭。 C和D用atoi比C#快约5-8倍,比Java快3倍,Java比C#效率高得多。但是这四个比C++以iostream为基础的转换都快。C++用 Digital Mars/STLPor运行时间t 是 C#的2.5倍,用 Intel和VC6 CRT/STL 是 10倍。这是iostream的另一个致命弱点。但因此说C++效率低下未免有失公允(实际上,我正在为 STLSoft 库写字符至整型的转换,以和它 integer_to_string<> 的杰出转换性能相匹配。最初的结果表明它将优于 C的atoi,所以这个转换中C++也是优于C的。我希望能在本文刊发前完成此转换。请参阅 http://stlsoft.org/conversion_library.html 。 )

picalc 在这里各语言性能表现相近。 C#和Java几乎相同,比C,C++效率高上10%。C#的性能可以这样解释:它的浮点数操作效率高,这可以从f2I看出。但这不能理解Java的表现。我们可以认为C#和Java都能较好的优化循环,它涉及函数调用,但这有赖于深入研究。既然性能相差10%以内,我们只能得出这样的结论:各语言在循环结构性能方面差异不显著。有趣的是,Intel的优化这里没有实际效果,可能是因为Pi值计算太简单了吧。

Picalcr : 这里的结果可以更加确定一点, C , C++ , C# 和 D 语言的性能偏差在 2% 之内,我们可以公平地说它们对于递归执行的性能是一样的。 Java 却是花费比其他四种语言多 20% 的时间,加上 JVM ( Java 虚拟机)在超过 4500 次递归时堆栈耗竭,可以明显地从速度和内存消耗方面得出 Java 处理递归不是很好。 Intel 的优化能力在这里有明显的作用(让人印象深刻的 10% ),但我在这里并没有详细分析这一点。

Sieve: 相对简单的计算质数的算法(仅包含迭代和数组访问)说明 C , C++ , C# 和 D 语言在这方面事实上是一样的(仅 0.5% 的偏差),但 Java 的性能低了 6% 。我认为这中间大有文章, C , C++ 代码不进行数组边界安全检查,而 C# 和 Java 进行数组边界检查。我对这种结果的解释是: C# 和 D 语言在 FOR 循环内能根据对数组边界的条件测试对边界检查进行优化,而 Java 不行。由于 C# 是新兴语言, D 语言还没发行,这就让人不那么失望了。即使这不是为 Java 辩解,也不会让人印象深刻了。再说, Intel 优化起了明显的作用——增长 5% 性能,正如 picalcr 例子,这相对于其他语言更让人印象深刻。

Strswtch : 这个测试对于 C# 语言不是很好。即使是 C++ 低效的字符串类操作符 = = 都比 C# 的性能快 2.5 倍 (Digital Mars) 到 5 倍( Intel VC6 STL/CRT )。 .NET 环境下字符串是内部处理的,这表示字符串存储在全局离散表中,副本可以从内存中消除,等值测试是以一致性检查的方式进行的 ( 只要两个参数都是在内部 ) 。这明显表示内部修复机制的效率严重低下或者一致性检查没有建立 C# 的 ”string-swatching” 机制性。 C# 的这种严重低效率更让人倾向于后者,因为想不到个好的理由解释为什么会这样。正如例 8 所示,变量 1 的字符串实例是文字的,都在内部(事实上,这里有个限制没有在例 8 中表示出来,在变量 1 的预循环中用来验证参数是真正在内部的)。

当在变量 2 中假装进行一致性检查,发现仅 Java 的性能有明显的提高,从 2% 到令人印象深刻的 30% 。很明显 Java 在使用一致性检查所取的性能是不切实际的,因为在真正的程序中不能限制字符串都是文字的。但这可用来说明支持内部处理和提供可编程实现访问这种机制的语言的一种性能。讽刺的是, C# 作为这五种语言之一,它的性能是最差的。

Strcat : 表 2 以微秒( μs )显示了五种语言每一种在这个例子中的四个变量的花费时间。本文没有足够的篇幅来细节描述 17 执行代码,但尽量保持例子 7 的四个变量的真实面貌。变量 1 和 2 在所有语言执行代码中涉及以堆栈为基础的字符串内存,同时 C , D 语言在第三个变量中、 C , C++ , D 语言在第四个变量中用结构内存,这在某种程度说明他们的性能优于其他变量和语言的执行代码。本例子的 C , C++ 的讨论适合于 Digital Mars 版本,因为很明显的 Intel 所带来的性能提高被 Visual C++ 运行时刻库的效率(非期望)所抵消,事实上 Intel 的每种测试的性能都比 Digital Mars 的差。

第一种测试是以默认的方式执行的,很显然 C# 和 D 语言的性能都好于其它语言。很奇怪的是 Java 的性能最差,因为 Java 被认为有能力在一条语句中把字符串序列解释成字符串构件格式(在变量 4 中手工转换)。

第二种测试,众所周知它的格式不好,透露了很多东西。它的劣性根据语言而不同,对于 C# , D 和 Java ,包含在连续的表达式中连接每一项,而不是象第一个变量在一个表达式中。对于 C ,它简单地省略了在开始多项连接重新分配之前的的内存请求。对于 C++ ,它使用 strstream 和插入操作符而不是使用 str::string 和 + ()操作符。结果显示所有语言不同程度地降低性能。 C 内存分配器的附加轮询看起来没有起多大作用,可能因为没有竞争线程造成内存碎片,所以消耗同样的内存块。 C++ 改变成 iostreams 使代码更紧凑,但没有转变数字部分,它们仅被插入到流当中,所预计的执行结果验证了这一点。 D 因为这个改变降低性能许多( 32% ), Java 更多( 60% )。很奇怪的是 C# 性能在这里仅降低很小,少于 7% 。这让人印象非常深刻,意味着可能在 .NET 的开发环境下因为 Java 字符串连接而形成的代码回查的警觉本能会衰退。

第三种测试使用 printf()/Format() 的变量不足为奇。通过使用部分 / 全部的帧内存, C , C++ 和 D 达到很好的性能。奇怪的是 C# 的性能仅是 22% ,简直不值一提。 Java 根本没有这个便利功能。(以个人的观点也是不值一提的) C#和D语言都能够在封闭的循环中,依照数组边界测试情况,优化他们的越界检查。

最大的 测试 -硬编码的性能-这个是很有趣的问题,因为 C++还是能提供超级的性能,如果能够依照问题的精确领域来编码,即使别的语言(C#,java)使用默认的或者性能优化的组件或者理念也是如此。在C#和java上使用他们各自的StringBuilders能够提供真实的效果,达到他们在这个集合中的最佳性能。然而,C#能发挥最佳效率的机制,还是不能与C/C++、D语言相比。更糟糕的是,java的最佳性能都比别的语言的最差性能要可怜,这是相当可悲的。

总之,我们说对底层直接操作的语言垂手可得地赢得那些转化率工作得很好的或者完全地在中间代码中,并且很显然地能够提供良好性能的语言用法对java是很重要的而对C#是不太重要的。请注意这一点。

Strtok . Table3显示了五种语言的每一个环节总共的时间(单位是毫秒ms)。同时他们的空间占用在这篇文章中并没有提及,但是所有语言的实现都是非常直接的并且很明显很接近在Listing9中列出的四个问题的本质。与strcat一样,基于同样的原因,使用Intel编译器的结果没有讨论。

第一个问题??分割字符,保留空格??显示C++版本是明显的胜利者,D语言要慢20%,C# 要3倍的时间。显然,这个与库的特征的关系比语言的更明显,

并且我使用 tokenizer比著名的Boost更多一些,那样的话,D语言要比C++快2倍,并且C#只慢20%。虽然如此,STLSoft的tokenizer是免费得到,我想我们应该把这个作为C++的一个胜利。(其实,template在这一点上的使用STL的basic_string<char>作为他的值类型,这个并不因为他的良好性能而闻名,这可能引起争论-我并没有使用一个更有效率的string,就像STLSoft的frame_string.总之,我认为这是个公平比较)。

第二个问题--分割字符,去掉空格--包含所有语言的实现版本。自然地,C语言赢得了比赛,它使用了strtok()函数,这个函数在创建tokens时并不分配内存并且直接写终结符NULL到tokenized string的结尾。尽管有这些不利条件,C++的性能还是很好的,时间是C的221%,比较好的是D语言,432%。

C#和java就很差了,分别是746%和957%。这简直是不敢相信的,C#和java运行的时间是C++的3.4倍和4.3倍,这三种语言都为tokens分配了内存。我相信这个是STL模型在处理iterable sequences时比passing around arrays更有效率的很好的例子。(请注意,我们有高度的信心这是场公平的关于C++,C#和D语言的比较,我写了三种tokerizer的实现,都是公开的,可得到的.)

第三个问题--分割句子,保留空格--显示了两件事情。第一,在这三种语言在实现这个程序的代价比实现单个字符更昂贵。第二,我们发现D语言取代了C++的最佳性能的地位,领先5%左右。C#继续被甩下,大概是另外两种语言的1.8倍时间左右。

第四个问题--分割句子,去掉空格--没有任何令人意外的,C++和D拥有大概一致的性能。C#比较不错(大概2倍的性能差距)java更慢(差不多3倍时间)。对于C++,C#和D来说去掉空格比不去掉空格导致一小点的性能损失。很明显的,在C#中,这么做比其他两个的损失更大一点。很有价值的是,这些功能在C#和D中是已经实现的。由于数组在C#中是不可变的,从tokens中返回一个没有空格的数组将导致重新分配一个数组。可是,D允许指派数组的长度,这个能动态地调整数组的大小这是个很好的例子说明D语言提供更好的效率。

总的来说,考虑到性能和自由度,我们能够说 C++是胜利者,D是很接近的第二名。C拥有最佳的性能,但是只支持一种类型的tokerization。C#表现得很差,java更甚。

总结

我们可以把结果分成3种情况:这些包括语言的特性,包括库的特性,或者两者都相关。这些只与语言相关的特性显示在 f2i, i2f, picalc, picalcr, and sieve scenarios,这些在语言选择上的作用是很小的。c#看起来在总体上是最好的,但是有点让人不能信服的是它在f2i中取得的优异性能并且是因为牺牲了浮点精确度而取得效率。(这一点需要更多大检查,我将在下一篇文章中去做。)

java是明显最差的 。

当讨论intel编译器的浮点计算的深度优化效果时,它很明显地显示,c和c++能够做的比C#更好。我讨论这些并不是说这些语言必须要更好的效率,而是说他们能比其他的语言提供更好的优化和增强的机会。这个大概是与本主题完全不相干的是,C和C++比别的语言有更多的编译器;更多的竞争,更聪明的人拥有不同的策略和技术。编译器的结果的运行目标是处理器,这将导致戏剧性的效果;我们仅仅在表面上探讨了这个庞大的问题,但是我不久将在以后的文章中回到这个主题。

Strtok 是唯一一个能说是library-only的,在这一点上C#干的并不好。虽然比java快,但是比其他的语言慢2倍或更多。同样令人失望的是VisualC/C++的运行库。在这一点上包括语言和库的效果的是i2str,str2i,strcat和strtswtch,描绘的并不是很清楚。C#明显的在string的拼接这一环节上比java好得多,但是明显地比其他的差。关于尊敬的C,C++,D,只是在一些环节上领先,在另外一些环节上相当地差。

很有趣的是定制的C++的库替换加上深度优化的intel的编译器。

总之,不可能做出定量的结论。从语言的表层来看, C# 和 Java 挺简单的,但低效的库降低了速度。我觉得,当相关的库还不如语言本身的时候, C# 的确实有微弱的优势;但反过来,当库逐渐完善,超过语言本身后, C# 明显就不行了。但还是能看到一点点希望: C# 还是有可能实现预期的目标,因为库比语言更容易实现组装。但是,人们认为由于跟别的语言相比, C# 和 Java 所用的库与语言结合的更加紧密,这些库就可以作为这两种语言效率的关键所在,至少对当前的版本来说是这样的。

正如我所作出的示范(从整数到字符)以及相关提到的(从字符到整数),有人可能会批评者并不符合这篇文章的原意。

更深入的探讨

本文主要探讨了一下 C# 对 C++ 和 Java 可能造成的“威胁”。总的来说,这结果虽然不怎么鼓舞人心,却足以让人吃惊。从效率上看, C# 跟 C 以及 C++ 这些过去的同类产品(假如它们是更高效的)相比只能算是一般水平,至少在基本语言特征的比较上是这样的。从某种程度上来说, Java 也是如此。(我承认我无法证明 C 和 C++ 相对于 C# 的绝对优势。在多年前我的毕业论文分析调查期间,我认识到令人乏味的结果正和令人兴奋的结果一样生动形象,但前者花费更小的经济支出。)

上述结论对于多处理器以及(高性能的文件/网络处理系统)等来说却不一定适用。不过,这确实很好地展现出了这些语言的基本效率以及更复杂的运算所依赖的最基本的库。

就 D 发展状况来看,现在就对它进行这样的归类似乎为时过早,不过它现在的表现确实不错。 D 的效率至少是 C# 的 167% ,其实大多数情况下还不止。有些人指出,目前仅仅还处于字符时代,只需几个人就能完成一个编译器及其附带的库。我猜测, D 有可能发展成一门强大的技术。

就我个人而言,作为一个对 C# 持有偏见的人,偶然还是会对它的性能感到惊讶。我热切地期盼着能将性能的对比分析延伸到更多领域:复杂而高效的内存的应用,多线程,进程通信,特殊文件处理,手写代码开销,这些技术都能让我们对语言进行更深入的研究。能看到 Inter 公司怎样通过定制的 C Library (比如 CRunTiny ;参考 http://cruntiny.org/ )和 STL (比如 STLPort )。

鸣谢

感谢 Walter Bright 为我提供了一份言简意赅的关于 D 的介绍,并及时地指出了一些当时被我忽略的优点,使我能对测试工程进行改进。同时也要感谢 Sun Microsystems 公司的 Gary Pennington ,他为我提供了关于 Java 方面的资料。还有 Scott Patterson ,他对本文的草稿进行了大量的精简,并对全篇文章进行了细致的检查。

28

主题

685

帖子

703

积分

高级会员

Rank: 4

积分
703
发表于 2004-11-9 10:00:00 | 显示全部楼层

Re:选择C#的理由

在本书的第一部分,我讲述了C#语言的许多基本方面——包括整型、字符型和浮点型之间的转换,循环、迭代算法,以及字符串比较、拼接和 特定符号统计 。正如我所说,对一种新的语言与它的类似语言进行全面、公平的性能比较,范围将会很庞大,所以本书沿用第一个例子并只对有限的重要的部分特性做测试。这种比较,以先前的 C#及它的库与C、C++、D和Java的特性对比为基础,主要在以下几个领域:

·类机制(封装与模板)

·异常

·内存— 重点

·大型文件的随机访问

·资源获取初始化

正如我在第一部分提到的,这里所做的测试只是针对程序性能的一小部分,硬件及操作系统的配置是测试语言 /编译器/库函数的机器的典型配置:2-GHZ,512-MB,Pentium IV,Windows XP Professional。 这里我们看到的任何结果揭示了在其它环境下可能的性能,这不说明可以假设这些结果就是正确的典型例子。 最后要说明的是,当我提到“C#比Java好”等结论的时候,一定是指“在特定的测试环境的条件下”,这一点请你铭记在心。

性能评价

所有的代码还是用和第一部分一样的编译器编译。 C#代码在.NET框架 1.0.3705(可从http://microsoft.com/netframework/免费下载)下,由Visual C#.NET 编译器v7.00.9466编译。D代码由Digital Mars D 编译器 Alpha v0.62 (可从http://www.digitalmars.com/免费下载) 编译。Java代码由J2DKSE1.4.1-02 ( 可免费下载 http://java.sun.com/ ) 编译。 C和C++代码 ( 单线程 , 包括 raii , 和静态链接库 ) 由使用 STLPort v4.5的Digital Mars C/C++ v8.33 ( 可从 http://www.digitalmars.com/免费下载 ) 和使用 Visual C++ 6.0的头文件和库文件的Intel C/C++ v7.0来编译。所有这些编译器均使用最优化处理 ( set for speed ) ; Intel 用-QaxW flag [2]提供两套编码路径:一个是针对有SSE2的Pentium IV处理器,另一个是针对其他所有的处理器——在运行时选择合适的一个。(因为Intel编译器只适用于Intel处理器构造,特别是在这个测试中的Pentium IV,这编译器的编译结果必须在此基础上评价,不能在其他的处理器上进行同样的过程,比如Athlon,甚至是旧版本的Intel处理器也不行。)

后面将要描述我们测试的假想的五种例子中的性能表现。他们都用2-GHZ,512-MB,Pentium IV,Windows XP Professional的配置,并沿用在第一部分用的性能分析系统。每一种语言所编程序的调试都执行7次,同时没有其他占线过程,去掉最高速的和最低速的,纪录平均值。所有的时间信息,通过仅区分相应时间区间的高性能计时器保存下来。(C用户调用Win32 API QueryPerformanceCounter() ;C++ 用 Win-STL ' s performance_counter [4], 可在http://winstl.org/找到 ; C# 用 SynSoft.Performance.PerformanceCounter ,可在 http://synsoft.org/dotnet.html找到; D 用 synsoft.win32.perf.PerformanceCounter , 可在http://synsoft.org/d.html找到;Java 用 System.currentTimeMillis() .)每一个测试程序都包含一个启动迭代的程序,这样可以将启动或缓冲时的影响最小化。

在每一个例子中的C#,C++和D的特征: C在所有 保存 封装和 raii( 类)的地方出现,因为它没有这两种例子必需的语言特征。 raii 没有出现Java,因为Java甚至连 Resource AcquisitionIs Initialization (RAII [1])的机制也没有,所以它仅仅作为在GC-kicking的一个练习。简单的说,我不会展示所有的语言的每一个例子的所有特性,但是所有这些都包含于在线记录中(连同产生他们的Digital Mars make-compatible make-file)。

下面就对测试性能的例子进行评价 :


封装 。这个例子( Listing1)的目的是比较不同语言(除了C)在类机制上的代价,这些语言并不完全支持相对于它们所做的所有类型的一般处理。尤其是C#和Java一般不能存储和使用它们的内置类型实例,比如int,long,bool等等,同样,对于从这些实例派生的类型也不行。相反C++和D都支持模板,它可以给任何类型的一般性操作带来方便。在以后的文章里,我将详细地讲解容器。但在这个章节将不会用到它,如果提及它反而会分散对于封装意义的理解。C#和Java中未封装的变量传递ints给异或函数,封装变量传递整形值在Int32(C#)和Interger(Java)的实例中。注意Java不支持模糊的封装(这被证明是好的因为编译器将对于这种封装带来的低效给予警告),所以实例必须明确地被创建(在列表1中标记“Java ” 处可以看到)。 C++和D的运行是简单的使用模板,这就是它们的泛型机制。(在这里我不对“一切都是对象”的模板的好处作深入探讨,但是我将在这期的姊妹篇Stepanov camp中对它做详细讲解)。

异常处理 。异常的语义在各种语言之间有很大的区别。 在 C++,异常处理和调用析构函数一起沿着堆栈操作下来,然而在有垃圾回收的语言中,这些对象被丢弃,直到将来的某一时刻被收集。 这样,就无法对各种语言进行有意义的比较。所以在这个例子中,只能通过将一段 using/aware代码与另一段没有异常处理支持的相同代码进行比较。这个例子有三种测试,它们的目的就是去衡量使用和抛出异常处理的代价。第一(Listing 2)是比较使用异常处理和通过函数返回值来传递信息给调用者这两者的代价。第二(Listing 3)是评估在try-catch模块中执行的代价(尽管没有异常处理被抛出)。最后(Listing 4)就是评估遍历执行try-catch模块的代价(同样,这没有异常处理被抛出)。










Mstress。 这一个例子的目的是,在假设分配比回收优越的基础上,看看垃圾回收语言是否在高的内存利用率上有重要的非线形关系。这里存在四种测试,每一个都牵涉到分配与释放( C 和 C++)或者丢弃(C#, D, 和 Java)在一个时间段内的内存块。这些块大小是通过相同的伪随机数算法生成的(详见文章的第一部分),这能使块的大小处于1至1000之间,从而在确保每种语言在相同的环境下测试时仿效现实世界中的分配。第一种测试(Listing 5)通过一个不断循环的变量分配了1000个内存块,目的是看看频繁地分配会不会“留下”更多的碎片,这些碎片会阻碍分配的进行直到突然产生明显的GC收集。第二种测试(Listing 5)增加了1000次循环中分配的次数,以测试由不断增加的内存负担引起的性能的 损失 。 第三和第四种测试(没列出来)跟前两个相似,它们增加了另外的内存块和分配/回收的交叉活动,目的是为了模拟不断增加的内存碎片。 C使用malloc()/free(),而C++,C#,D和JAVA用new[]。



rafile 。这一个例子( Listing 6)评估各种语言从大文件中随机读取的能力。它利用某种伪随机算法(详见文章第一部分)来模拟在大文件中的查找/读取。这里用到了两个文件,一个4.2MB,一个21.1MB。在每一个查找点,四个字节被读取,并且和一个运行值进行异或运算(这样有利于证明所有的语言以相同的方式查找和读取)。有意思的是,原始的运行程序读了32位的整数,但不幸的是,JAVA用了network byte-order而不是host byte-order,这与C, C++, 和D的machine-order reading是不相容的。




Raii(类) : 这最后的例子目的是为了评估没有内建支持这个非常重要的语言特征(很不幸 C#和Java都没有)而付出的代价。此例子表达了通过资源句柄XUser 类型从XFactory资源管理器分配X资源到客户端代码(见代码段7)。在C++和D语言中,XUser 提供一个析构函数,在析构函数里分配的资源自动地回收到资源库,资源在这种情况下是个信号量,.NET和D的执行代码可从 http://synsoft.org/dotnet.html 和 http://synsoft.org/d.html 下载。C#、D和Java 提供它们自己抽象的同步机制,但我需要一个信号量去引起交叉线程的竞争,去保证同样的同步机制以便所有语言的进行生动的比较。此信号量在每种语言执行代码中都有最初可获得的五个键值。




尽管 C#的XUser 类有一个析构函数,但此析构函数仅在实例进行垃圾碎片收集时被调用。(C#使用析构函数的语法,但编译器把它翻译成 CLR (公共语言运行时刻)的 Finalize() 方法。)这种情况跟 Java一样。C# 还尽量更多地支持习惯用语(虽然它支持的还不够),这是通过使用引用声明来实现的,正如表7所示。(为了方便,“using-ed”类型必须实现 IDisposable 接口,否则声明无效),一个明显的问题是类的使用者必须承担确保语义的正确的负担,而不是类的开发者。这是奇怪的二分法:C#是面向对象的,但它依靠类的使用者对类的内部执行代码的足够理解以决定是否需要此类的直接析构。C++和D语言过程明确,由类自己决定:C++通过析构函数的执行,D提供析构或终止函数,类声明验证关键字 AUTO (没有这些,它按C#和Java的方式运行正常的垃圾碎片收集程序)。

C#语言使用 USING 声明(和 IDisposable 接口)的一个可替代方法是依靠垃圾回收。在形成这个情况之前,我假设当内存使用率达到触发阀值或者主线程空闲/等待的时候启动垃圾碎片回收。(我不知道这种误解是怎么形成的,或许只是异想天开)。当我在执行 Raii2 例子的最初版本的时候(变量没有使用 USING 声明),处理进程马上挂起。很明显,在其他线程堵塞时垃圾碎片收集没有触动,只有在内存消耗到一定程度才触动。因此,为了程序执行,我们被迫在另一个工作线程中自己剔除垃圾碎片收集。当然,这在实际编程中是非常可笑的执行策略,执行结果证明了这一点。

28

主题

685

帖子

703

积分

高级会员

Rank: 4

积分
703
发表于 2004-11-9 10:01:00 | 显示全部楼层

Re:选择C#的理由

结论

大部分的结果是以绝对运行时间数(全部循环或每循环)或者 C#的执行时间和C,C++,D,Java分别的百分比来表达的。在后一种方式,围绕着百分刻度的结果表示C#的性能比率,高的值表示C#的性能优越,低的值表示相对低下。 Raii 的情况是个例外,在这里结果是以C++(Digital Mars)时间的百分比表示的,由于垃圾碎片收集代码的消耗,调用时间值是以对数刻度表示的。这说明在对数比例上归根于非常高的垃圾处理花费.




  

BOX . 从图 1中我们能看出,在合理范围里(C#语言的97%-271%)对于所有编译语言解除封装操作(就简单整形数据)成本的差别:最差的是C++(Digital Mars),成本是C#的271%,最好的是C++(Intel) 成本是C#的97%.假设执行相对比较简单的 异或 运算, C#几乎可以像 Intel那样出色,你对这个并不会感到很惊奇,但你相信C#要比C++(Digital Mars)和D模板实例快两倍.我有些惊讶C++(Digital Mars)这个相对弱的性能,特别是和D比起来它要慢得多,然而他们出自同一开发商。


显然这个封装花费是有意义的 ,相当于C++模板花费4-10倍.这个测试仅仅包括整数,相对来说可能不能完全反映特殊类型.虽然如此,我们能有把握断言这个模板轻易赢得了性能战.有趣的是就他们各自的成熟度而言,C#要比java稍好,

差距并不大 (小于4%).(我想可能由于.NET处理目标JIT3。【3】)。更有趣的是,事实上在未封装的形态下,c#明显比java快.




除了图 1以外,有人认为,不抛出的异常variants都是是比抛出的异常副本要快的(如图2). 用 c和c++,Intel完成任务要比所有其它不抛出的异常variants快.c#紧随其后。似乎c,c++,D(不考虑编译器)在异常处理上花费大致相等,符合win32异常处理机制的预期限制因素.我们能看到三种语言在抛出异常variants上的差别是比较小的,很可能的原因是由于有相关的额外的管理消耗关系 (c++规定执行破坏功能的堆栈是从捕获点到抛出点的来回).

我发现有趣的是在抛出异常variants方面c#和java的执行是有关系的.

因为这两种语言语义在异常抛出处理要胜于返回当前错误值 ,也胜于其它语言,

尤其 c语言所呈现的关系执行调用.我非常赞同Brian Kernighan和Rob Pike

(<>,Addison Wesley,1999)所说的例外仅仅在例外条件的使用,并不是必然会发生的.然而人们能容易看到为何服务器在处理巨大数量的数据的时候可能出现的例外,而经常担心性能的问题.正是如此,java以小于其它语言25%的消耗给人深刻的印象.由此我们限定win32,采用两个有代表性的差不多的c编译器来构造例外处理,虽然没有定论,但是我认为java使用了不同的机制.

那么c#的异常抛出性能如何呢?在相关条件下,相对其他语言而言,使用异常处理语句是不使用异常处理语句的21-77倍,而c#则是191倍;鉴于Java的出色表现,.net工具应该引起注意了。

Except-2. 这个图表是为了弄清楚在一个 try-catch范围内异常处理语句的执行是否耗时,如图3,大多数语言的表现同预期的一样:c、c++和D对于Digital Mars是类似的;Intel c 和c++比其它都好.c#要比java快.相比较就性能而言,

c#是很优势的,令人感兴趣的是,c#在进行异常处理是耗时比不进行时还要少.产生这种结果肯定是有原因的(这个结果是经过了多次验证的), 但由于对于c#异常处理机制我没有很深的认识,我不能做出合理的解释.同样,虽然并没有定论,对于D来说,是否进行异常处理对结果似乎并无影响!.
我认为这是因为它不用清除栈内数据, java也有大致相同的自由度,并且表现出的差异也并不大。




except-3 如图表 4所示,测试结果与预期大体相符。除了D编译器的结果中微不足道的差异,所有的语言在遍历异常处理语句的时候都比不进行是耗时要长,尽管如此,我仍然对在这两种情况下差异如此之小感到吃惊。我们通常认为使用异常处理机制会耗费时间,并且我认为遍历异常处理语句会在启动和完成上耗费大量时间;事实上,我一开始就预期两者的差异将会非常地显著。然而,在这个测试当中,两者的差异相当不明显。

但是这个结果并不难理解,导致差异如此小的原因首先在于:这次测试太过于纯粹。这次测试的所有异常处理(类型)都是 int型的。尽管并没有异常被抛出,这样做的目的在于避免在异常处理体的构造(和析构)上耗费时间和消除各编译器处理机制不同所造成的影响。其次,本次测试中没有基于框架的结构体,因为在C++中,尽管析构函数并不会真正被调用,析构函数调用方面的准备也是必须进行的,这样也会造成时间的耗费。(注意析构函数无论异常是否被抛出都会被调用。)这些耗费都会造成测试的不公平,所以我们选择了int型。注意到程序不论是因为异常抛出而中止还是正常退出,析构函数都是要被调用的。所以它仅仅做为勾子而被添加进这些额外的函数调用中。然而,我认为所有的这些因素并不是很充足,它们仅仅使我们可以从那张表里知道当不使用的时候异常的开销是非常小的。很自然,这也正确的表明了一个人对所有语言性能(perform)的期望,当然我们要给它足够的信任.

metress-1 这个测试很清楚的表明了在不使用内存分配机制的展示 (exhibit)中不断增长的对固定数量的内存快(随机大小)分配/释放的循环的反应是非线性的。事实上,在很大程度上它们从没有线性(增长)的趋势:从性能上来说并没有什么变化(见图5),除了在一些低循环(low interations)上有所不同之外.(有趣的是,那些低循环的非线性是有意义的--占到了全部命令的50%还多--当然这仅仅是对c#和java而言).不管内存分配机制是否在每次特循环结束后都立即恢复对1000个内存块的释放,或者仅是简单的把它们交给GC在以后的某个时间处理,语言/运行库总是看上去几乎完全不受这些循环执行的影响.

在这组性能相对的测试中,我们可以清楚的看到一些它们之间的不同。在使用 Digital Mars 分配方式的语言中,C和C++的表现是最好的。Visual C++的运行库比使用Digital Mars的语言低大概2.5-3倍,对于使用Digital Mars 的语言和Visaul C++运行库来说,C和C++基本上是相同的。最后,很明显,Java 比C#慢了3-4倍,而比C慢了差不多7倍.




mestress -2

就像我们从图表中看到的,在一定数目的分配释放内存的循环中内存块(随机大小)的不断增长的反应是变量的引用是非线性表现的。这正是我们所希望的,因为在每次循环中分配的内存总量是按指数方式增长.每次循环所分配的内存的大小都低于10,000块.使用Digital Mars分配方式的C 和C++的效率依然是极为优秀的。只是效率表现上低于平均值,而这里也不得不提到java,它的表现同样不好.Visual C++的运行库(C和C++都适用)相对于C#,D 和Java来说,有一个不错的开始,但它很快就降到了一个很低的水平上。

Java在每次循环是使用10,000内存块之前的表现非常有竞争力,并在这一点急剧上升。D在整个测试中都几乎有着一致表现.几乎一直都是非线性的,这与C#很接近。

如果可能的话,我都希望看到一条总够长的X轴,C#在内存溢出的情况下仍然保持每次循环不超出10,000内存块的水平并预防这种情况的出现。(D和Java似乎也做的到,但那也只是类似C#的行为一旦被发现就中止测试。)

mstress-3 和 4 从前两个测试(variants)看,除了时间的的增长,反复的交叉存取和块变量在执行上的表现并没有什么不同。曲线的走势或者说不同的语言/运行库的相对表现在这一点上没有什么明显的改变。

我认为C和C++,在使用外部free/delete的条件下,重新使用的新近释放的碎片。相反的,我很难想像出C#,D和Java 是如何使用垃圾收集机制周期性寻找每次循环所分配的内存从而尽可能的减少由内存碎片所引起的负面效应的,或者在这个跟本就没有产生碎片.除去这些不同,这两种方式的表现还是很相似的。

这只是一种理想的我们所希望的分配机制的表现 ---毕竟,那是一个很极端情况,所有内存分配都可以在给定的周期内完全返回---虽然程序的执行都达到的目的。

rafile. 这次测试中我所希望是那些语言实际 (效率)上并没有什么不同.除了C++的执行也许比其它的低上几个百分点.


图表 7中可以看出,C#的在文件的随机存取上比C(使用Digital Mars)要好,但低于C++(使用Intel和VC6的连接库),和D与Java现表现基本持平。但是C++运行库表现出令印象深刻的性能。

从所有这一系列测试来看,Intel 似乎已经能够产生性能不错的代码了。但是它的连接的运行库头文件却是Visual C++ 6.0的,这个(大概)不是由Intel编译器产生的。因比它几乎是以压倒性的性能优势超过了DMC,这主要是由于各自的Digital Mars 和微软Visual C++运行库的性能。(我承认有点吃惊,对于些测试结果正好可以反对两个卖家的名声--应该或是不应该得到的)。这也表明一个人的偏见是非常没有理由的。

另一件值的注意的事就是对不同大小文件访问的开销都非常的小。这也表明所有的语言都利用操作系统的优势很轻易的达到了相同的程度。




raII . 从图 8中我们能看出使用statement的C#的表现只有C++析构函数效果的一半。D的性能与之相当,虽然产生的代码中有错误(或者是D的Phobos运行库),当进程创建的对像超过32,000左右的时候会使该进程挂起,从而防止过多(一个以上的)通讯中的数据点被确定的使用.在这组单独的测试中我们可以看到RAII对C#和D的支持是很完善的,但并不如想象中那样优秀。如有你要做很多scoping--并且你想,也许要足够的robustaness的帮助---你最好还是选择(stay with)C++。当依赖于.NET的垃圾回收机制时,因为处理过程只是简单地被挂起,所以C#的性能比较难以测算。可是我们不希望.NET经常调用它的垃圾回收线程,但我找不到理由解释为什么回收线程不以较低优先级在内核中等待,当有垃圾对象需要被回收和主线程空闲时再进行有效的回收。当然,我在垃圾回收方面不是专家,并且可能有一种合理的理论原因说明这为什么不是一个好的处理方法。

GC(垃圾回收)即使响应的更灵敏,我们可以在它的性能结果上看出效果并不与之匹配。对应第二组数据指针的方法表明了GC每隔1ms被触发一次。

GC(垃圾回收)的方法比使用使用声明慢了近千倍,所以这不仅仅是一个技术问题的范围。自然,这跟关于这个例子的假设有关,同时跟几乎每个人都用垃圾回收机制去管理频繁争用的资源的释放这个不辩的事实有关,但我没预计到这个变量的性能是如此之差。同时这也给了读过Java/.NET书籍的C++程序员对于书籍建议(或许说依靠)使用终止函数来清除资源而感觉疑惑的一个解释。

结论

在第一篇文章,我对于不同的环节得出不同的结论,这些环节或是语言本身造成的,或是库或者二者造成的,在这里我也这么区分,因为语言特征而产生的影响的部分通常涉及:异常,封装 /模板和RAII的实现。文件存取部分可以看作直接对库函数的操作。(没有任何一种语言阻止写替换操作,虽然这样做并不是很容易)。内存管理部分受语言和内存的影响??虽然我不清楚可以用哪一种管理机制去替代C#和JAVA的缺省的内存管理机制,但是对于其他语言这是很简单的。

封装。当我们比较封装和模板时,我们可以看出模板明显比封装出色很多。我相信这不会让大家感到很惊奇,但是事实上封装消耗的系统资源是模板的十倍。自然,作为模板类库的作者( http://stlsoft.org/ ),我得出这个结论也许带有偏见,但是这些数据在表示更广泛的情况时,他们自身就说明这一点。在后面,我将说到容器和算法,我们将看到比预计更多的这样的结果。

至于有关异常情况,我想讲四点:

1.在所有的程序语言中,使用异常处理从调用的函数过程中返回,而不是保留他们作为异常事件的指示,将导致执行花费巨大的成本。

2.C#的异常处理机制相对于其他被测试的语言效率是极低的.

3.在有异常处理的上下文环境下运行(比如用try-catch的情况)对于性能没有多大影响,除了C#,它为了提高性能(实际上,我对于这种结果很不理解,并且怀疑这是.NET运行期的一个人为结果而不是C#/.NET的一般特征。)

4.交叉于异常上下文的执行(比如说进入try-catch和/或 离开try-catch)对于系统性能的影响基本上是非常小的。

Raii. C#支持的RAII例子比C++在性能方面差,虽然不是很多,但与D相比差别无几,基本一致。(这种一致指出了在处理堆分配对象的确定性析构时的基本限制)然而,从理论观点,或从易用性和/或稳健性的实践观点来看,这里还是有很大差距的。C语言缺乏机制可以解释为年代已久,并且它是一种程序语言。很遗憾,java缺乏这种机制,但是它只是可以解释为忽视了。(至今为止我们已经用java8年左右了,所以“忽视”可能也有些牵强。)C#在这方面的努力还不成熟(因为它更多地依赖类的用户而不是类的作者),很奇怪的是C#/.NET很多优点都是在Java中被看作瑕疵/遗漏的地方,比如属性,输出参数,和无符型。

Mstress. 这个内存测试的目的是证明如果频繁的内存分配加上垃圾回收机制是否会导致—— C#, D, 和Java这些包含垃圾回收的语言严重的非线形,这明显没有。这个结果可以看出所有的语言/库表现的非常合理的。这里我们可得出几个有趣的结论:

1.C语言和C++语言,提供正确的库支持,他们内存分配空间最快。毫无疑问,部分原因由于它们没有初始化字符数组的内容。根据结果,我重新对C语言进行测试,用calloc()替代malloc(),测试结果很接近C#,虽然仍然会高出5个百分点。

2.内存碎片(只要轮流存取第三和第四个变量)不会在很大程度上影响内存分配的性能,不会增加总体负担。

3.如果垃圾回收器安排在没有使用的内存区之后不执行(我假设这是可以的),这将不会对性能有太大的影响。我假设,这说明了C#是第一个会内存枯竭的语言,所以我们可以假定它们通过使用大量的内存空间在内存分配性能方便取得平衡,并且相信在现实的环境中有这种机会运行垃圾回收。

4.通常更希望体面地降低性能?就象C(Digital Mars)的方式??而不是在各个方面都有很强大的性能,然后在某些未知的阀值化为乌有:在服务器环境下,某一时刻提供一个慢点的服务比导致崩溃可能要好。由于对Digital Mars和Visual C++,C和C++的运行实际上是相同的,我们可以假定因为与通过new操作符的调用的交流而增加的成本可以忽略,并且在C和C++之间没有本质的区别。

5.C#内存分配时间会比Java快上3~4倍。

总的来说,这个结果并不象我想象的那样:我期望C和C++在中小应用比 C#,D,和Java稍微落后,但在大型应用中远远优于。真是学无止境。


Rafile . 对于文件的随机存储结果可以看出一些重点。我认为最重要的一点是仔细的选择库。之所以C++的运行效果看上去要比C和其他的语言好很多,就是因为库的原因。当把C++的性能(Intel 和VC6库)和其他语言/编译器进行比较时,用其他任何一种都很给人留下深刻的印象。自然,这边的例子是故意的,但这么大的性能差别的事实——比C#和 Java快23倍,我们可以期待在现实情况中性能的有意义的差别。(这里,再次证明了偏见是错误的:我很厌恶所有的io流,从很多方面很容易能看出它的效率很低,当然也不能说全部这样。我对于这样的性能印象很深刻)。

详细的总结了C#的性能以后,我想就我们在其他语言方面的研究发现做一个简短的介绍。

1.C处理异常事件是不错的,特别是在存储和文件(提供正确的库连接)有非常好的表现;它能提供所有我们能预期的效果,但是与后来的高级语言相比,吸引不了更多的学习者。

2.C++处理异常事件也不错,特别是在存储和文件(提供正确的库连接)有非常好的表现,而且包含模块的概念和有效的RAII;它始终得到我的钟爱,我认为它在未来的很长一段时间内都是编程的最佳选择。

3.D在异常事件处理上不是很太好,在文件处理上处于一般水平,在存储方面有比较合理的相关性能和非常不错的线性度,有模块和RAII(虽然语言本身有很多的bug产生,使得很多的处理过程被挂起);当这语言经过它的初级阶段,期望会得到不错的效果。

4.Java在封装和内存管理的性能上表现最差,不过它有好的异常事件处理,但没有一点模块和RAII的概念;它不指望会达到真正的美好,我认为C#/.NET可以衡量出它的高低,至少运行在Windows平台上 。


摘要

从第一部分开始,就给出全文的延伸的意思,从性能方面,如何能更好的用C#/.NET写出很好的软件。在第一部分里,一些结果虽然不是很值得注意,但是有很多的地方还是令人叫奇的(至少我是这样的!)。

这些研究结果展示了C#可以提供非常好的性能效果——至少在我们测试的范围内是这样——同时不能把它如同象过去把Visual Basic和一些扩展版本的Java当作一种性能差的语言来对待。对我而言,我比较注意语言运行的性能,当然,他们的表现也会超出我的预期效果。我所提出的严肃评论是从语言特征的观点和对多个编程范例的支持方面得出的。

作为一个最初使用C++的程序员,我从特定的观点得出这些比较结果。这可以从RAII和封装测试以及它们的语言解释上看的出来。在某种意义上来说,这就好比比较苹果和橙子,对于不同背景的人们可能会对我为什么如此强调模板和确定性析构感到疑惑,没有这些他们也进行的很好。

毫无疑问,模板给C++带来了非凡的革命,它能支持群组合作,或者独立运行,范例是无可比拟的。Java由于缺少模板和强制要求任何东西都是对象,而被批评了很长时间。.NET框架在这方面做法一样也很让人失望;可能由于Java缺少模板而他们可以在.NET环境下得到(他们确实达到)让更广的开发团体感到信赖并接受了它。

缺少对RAII的支持对GUIs(通用用户接口)是非常好的,这是在严格的框架内操作的软件——即使象J2EE这样精密复杂和高吞吐量的语言,和非关键的程序。但复杂的软件不必要因为它而复杂。在最好的情况下,到处都是最终模块,只要在Finalize()里函数里添加Close()方法就可以了。在最坏的情况下,滞缓或者随机的资源泄漏都会导致系统的崩溃。更甚于以上的,如果OO(面向对象)——就象C#和Java所有都是对象——是你的目的那更让我不安,第一个要失去的就是对象自己清除自己的责任,我没办法理解这个。(我知道一个非常出名的C++程序员——当然我不能告诉是谁,他告诉我当他在课程中突出RAII和模板的重点时,那些非使用C++的人们露出的滑稽表情,让他感觉好象他丢失了什么东西)

D是使用垃圾回收的语言,默认是非确定性析构,有很多地方与C#和Java相似,但是尽管如此,它还是兼容支持RAII的习惯同时具有模板。并且它是一个人写的!我不明白为什么我们对Java或者C#(.NET其它语言也是一样)印象如此深刻,即使有它们支持RAII和模板的缺点,而我们又能比较这些。有可能C#/.NET成功的原因和Java一样,有大量的,有用的库文件(C和C++应该从中学习,D应该去发展),和有一个强有力的后台。

最后,我想说对于所有的性能结果的比较分析,你必须明智地使用这些结果。我努力使自己公平,选择我相信是公平和有意义的测试。但你一定要判断的出这些例子仅代表了广大可能性的的一小部分(不是无限意义上的),它们不是实际的程序,仅仅是测试而已并且把它简化了,其中不可避免的带有我个人的某种偏见在里面。通过逐步的方法,我希望降低这些因素。但你在阅读和使用这些结果的时候要保持留意。

感谢

我要感谢 Walter Bright提供给我最新的Dv0.62版本,能够完全的测试异常事件。感谢Intel的David Hackson给我提供了C/C++编译器。还要感谢Scott Patterson帮我选择了切合实际的测试方法(他总是不断的在我烦躁、偏题、愚蠢的时候提醒我)。还要感谢Eugene Gershnik 对于我的一些断言给了我严厉的反驳,帮助我适当地注意一些防止误解的说明。

190

主题

1801

帖子

2096

积分

金牌会员

Rank: 6Rank: 6

积分
2096
QQ
 楼主| 发表于 2004-11-9 12:36:00 | 显示全部楼层

Re:选择C#的理由

马上要去考试,看了下结论,楼上转载的辛苦拉: )
结论给出了一些结果,却还是没有明确的答案
测试也似乎不全面,winxp作为了测试平台,似也没有用不同的compiler

另外,我也同意c#和java各有千秋,其中并没有不可逾越的差距,差距都在可以改进的范围之内

我想问个问题,也是我最初在选择语言时很郁闷的一点
作为一个学生,该怎么去学习C#呢?除了少数达人,大家都去买.net似乎不现实吧

41

主题

340

帖子

345

积分

中级会员

Rank: 3Rank: 3

积分
345
发表于 2004-11-9 14:03:00 | 显示全部楼层

Re:选择C#的理由

      其实也犯不作这么为难,C#和JAVA各有千秋,随便先选哪个先学都可。学懂了一个,另一个也很容易上手。建议先学java,不然你先学了C#后会觉得用java太麻烦(因为没有VS.NET)。
     我学C#的原因很简单,本来是用j2ee作一个项目,由于难度较大,开发周期太长,用户极为不满。老板听说.NET开发效率高,马上命令用.NET搞。于是我们只有放下java啃C#,好在微软的VS.NET搞得很好,不清楚的地方可以看中文MSDN,很快将项目搞定了。我的体会是,其实相对语言来说,程序架构更重要。
     目前国外,.NEY的开发也开始火起来,估计过两年.NET在国内也是这样。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-6-10 11:56

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表