银河

SKYIV STUDIO

  博客园 :: 首页 ::  ::  :: 订阅 订阅 :: 管理 ::
  105 随笔 :: 2 文章 :: 753 评论 :: 22 引用

最近一段时间,我在 Timus Online Judge 网站做 ACM 题

首先,让我们看一下 Timus 1114. Boxes

这道题要求计算出将两种颜色的球放到盒子中的各种组合的数目。我们发现用同样的算法,C# 程序居然比 C++ 程序慢 62 倍。

真的是 C# 应用程序的性能就一定很差吗?不是的。实际上在这道题中,使用的算法是非常高效的。上面的 0.001 秒和 0.062 秒已经分别是 C/C++ 程序和 C# 程序在 Timus Online Judge 网站运行的最短时间了。毕竟 C# 是托管的应用程序,要在 CLR 环境中运行,第一次运行时需要进行 JIT 编译。最小的基本开销要比 C/C++ 应用程序大。

接着,我们再来看看 Timus 1219. Symbolic Sequence

这道题要求输出满足给定条件的一百万个小写拉丁字母。还是使用同样的算法,C# 程序比 C++ 程序慢 15 倍,比 C 程序慢 64 倍。

这次,不能用最小的基本开销来解释了,因为这些程序运行的时间已经不算很短了。但是,这道题还是有些特别的,它的时间主要花费在输出大量的(一百万个)字符上。C# 程序是调用了一百万次 Console.Write() 方法,C++ 程序调用了一百万次 std::cout << c 语句,C 程序调用了一百万次 putchar() 函数。应该是这三种方法的不同效率造成的差异。如果把本题的算法稍做修改,使 C# 程序只调用一次 Console.Write() 方法输出全部一百万个字符,则其运行时间从 0.968 秒下降到 0.093 秒。

现在,让我们来看看 Timus 1152. The False Mirrors


这道题说述消灭怪物的故事,要求计算出故事中主角受到的最小伤害。还是使用同样的算法,我们终于看到 C# 程序和 C++ 程序的运行时间差不多了。

不过,坦白的说,实际上这道题我使用的算法不是最优的。这道题最优的算法使用 C++ 语言实现,运行时间只需要 0.001 秒。我不知道该算法是什么,如有谁知道的麻烦告诉我一下。:)

由于大多数 ACM 题目使用好的算法时需要的时间是很短的,所以如果用 C# 语言做题的话,基本上会发现比 C/C++ 语言慢很多,但是一般来说也不会超时,除非你使用的算法很差。下面就有一个例子,就是 Timus 1081. Binary Lexicographic Sequence


这道题要求给出第 K (0 < K < 109) 个 N (0 < N < 44) 位二进制数,该二进制数不得有相邻的“1”。在 Accepted 的 C# 和 C++ 程序中,使用了时间复杂度为 O(N) 的算法。而在 Time limit exceeded 的 C++ 程序中,使用了时间复杂度约为 O(1.618N+2) 的算法。

所以关键还是算法,而不在于程序设计语言。

何况,托管应用程序的性能在某些应用场合实际上有可能超过非托管的应用程序。例如,当 JIT 编译器在运行时将 IL 代码编译成本地代码时,编译器对执行环境的认识比非托管编译更加深刻。

JIT 编译器能判断应用程序是否运行在一个 Core 2 Duo 的 CPU 上,并生成相应的本地代码来利用 Core 2 Duo 支持的任何特殊指令。通常,非托管应用程序是针对具有最小功能集合的 CPU 编译的,不会使用可提升应用程序性能的特殊指令。

JIT 编译器可能判断一个特定的测试在运行它的机器上是否总是失败。例如,假定某个方法包含了一段代码判断主机上的 CPU 数多于一个时才执行的语句。如果主机上只有一个 CPU,则上述代码将导致 JIT 编译器不生成任何 CPU 指令。在这种情况下,本机代码将针对主机进行优化,最终的代码变得更小,执行得更快。

应用程序运行时,CLR 能评估代码的执行,并将 IL 重新编译成本地代码。重新编码的代码可能重新组织,根据刚才观察到的执行模式,减少不正确的分支预测。

posted on 2008-07-02 23:15 银河 阅读(2453) 评论(28)  编辑 收藏 网摘 所属分类: 算法

评论

#1楼  2008-07-03 00:24 Angel Lucifer      
.NET 比 C/C++ 慢是事实,Microsoft 提供的资料宣称 .NET 大约是 C/C++ 执行效率的 90%。XNA 则提供资料宣称 Managed DirectX 效率可以达到 Native DirectX 的 98%。

基于 C/C++ 开发人员普遍功底扎实,而 .NET 开发人员则相对的欠缺,偶对 .NET 在实际开发中的应用程序性能表示担忧。
  回复  引用  查看    

#2楼  2008-07-03 00:30 free blogs [未注册用户]
来解释了,因为这些程序运行的时间已经不算很短了
  回复  引用    

#3楼  2008-07-03 01:03 U2U      
算法才是根本
  回复  引用  查看    

其实比较C#和C++的速度是没有什么意义的,C#再牛速度也不会超过C++,这个是一定的,按照博主的比较,这个结果已经很让人满意了。

C#的优势在于快速的开发,稳定的内存管理,这些都是C++无法企及的,牺牲速度换来程序员的省心已经是当今程序开发所追求的目标了,因为大部分程序员都不是负责设计系统核心级或者驱动级的程序的。不然的话,也就不会有Python和Ruby如此流行的现象了。
  回复  引用    

#5楼 [楼主] 2008-07-03 08:01 银河      
--引用-(1楼)---------------------------------------------
Angel Lucifer:
基于 C/C++ 开发人员普遍功底扎实,而 .NET 开发人员则相对的欠缺,偶对 .NET 在实际开发中的应用程序性能表示担忧。
--------------------------------------------------------
基本同意你的上述观点。但是:
而 .NET 开发人员则相对的欠缺,这句话应该加个限定词:有些。
因为很多 .NET 开发人员也是(或者曾经是)基于 C/C++ 开发人员。所以说功底相对欠缺的 .NET 开发人员大部分(这应该只占很少比例)是没有学过 C/C++ 的开发人员。
我不但对 .NET 在实际开发中的应用程序性能表示担忧,也对 C/C++ 在实际开发中的应用程序性能表示担忧。实际上,应用程序的性能取决于其算法,取决于开发人员的素质,而不是取决于所使用程序设计语言。
  回复  引用  查看    

#6楼 [楼主] 2008-07-03 08:08 银河      
--引用-(3楼)-------------------------------------------
U2U: 算法才是根本
--------------------------------------------------------
非常同意。知音啊。
  回复  引用  查看    

#7楼  2008-07-03 08:36 Anders Liu      
C++也可以写Web应用的吧?那好,如果博客园的程序用C++来写,大概需要多久?用C#呢?博客园的速度您还满意吗?

同样C#也可以写游戏啦、数学运算啦,但C++用起来可能更顺手。

没意义。
  回复  引用  查看    

#8楼  2008-07-03 08:54 Hightree      
记得人月神话里的一句话:长期来看,人的低效才是最重要的.
以现在电脑的性能提升速度,对于一般的应用程序,尤其是基于web的,0.001秒和0.062秒,对用户来说,能感觉得到区别吗?但是,开发周期是三个月还是一年,就是显著区别了.
  回复  引用  查看    

#9楼  2008-07-03 09:07 AlexChen      
只是针对语言的比较是没有意义的,应该综合评定整个系统的性能,开发周期,稳定性等等,而不只是语言本身的运行效率.
  回复  引用  查看    

#10楼  2008-07-03 09:37 乱说 [未注册用户]
这样比较是不公平,同意楼上的说法,c#设计出来的目的和C/c++是不一样,但现在有这么的程序运行在java平台上,我觉得用这些来对java和C#来进行比较,也许还有说服力
  回复  引用    

#11楼  2008-07-03 09:49 Mainz      
物尽其用,现在很多企业也是用C#用作客户端开发而已
大规模并发实时核心交易处理当然是C或者C++

web应用还是Asp.net好用

算法,数据结构,哪个语言都重要;

语言不好就怪用的人算法不好,不公平。




  回复  引用  查看    

#12楼  2008-07-03 10:04 簡簡單單..      
基本同意..
  回复  引用  查看    

#13楼  2008-07-03 10:28 hoodlum1980      
C/C++的特点和追求就是高效,现在我更喜欢c和c++。
  回复  引用  查看    

#14楼  2008-07-03 10:44 Rainy      
楼主近来若干算法类文章非常值得阅读,我们在开发过程中往往忽视掉优秀算法的使用。

至于C#与C/C++的效率,基本上没有必要进行太过苛刻的对比,毕竟它们所适用的领域不同。当我们使用C#时,就放弃了最高的运行效率,同时换取了不错的开发效率、稳定性和安全性。在非核心应用中,对于开发和使用成本来说,多买一些机器来运行C#,比多花几个月写C/C++合适得多。

而一个糟糕的算法,会让C/C++/C#一起没完没了地耗费掉我们的预算。
  回复  引用  查看    

#15楼  2008-07-03 11:04 wfa [未注册用户]
@Hightree:
大并发的情况下,区别太大。 1ms和68ms的执行时间,可以导致并发用户几十倍的差距。
  回复  引用    

#16楼 [楼主] 2008-07-03 11:28 银河      
--引用-(14楼)-------------------------------------------
Rainy: 楼主近来若干算法类文章非常值得阅读,我们在开发过程中往往忽视掉优秀算法的使用。
--------------------------------------------------------
谢谢关注。
在这系列随笔中,虽然有一些算法的最优的,但也有一些算法并不是最优的,只不过能够 Accepted 而已。希望各位园友如果知道有更好的算法可以在该随笔的评论中告诉我。
谢谢!
  回复  引用  查看    

#17楼 [楼主] 2008-07-03 11:38 银河      
--引用-(15楼)-------------------------------------------
wfa: @Hightree:
大并发的情况下,区别太大。 1ms和68ms的执行时间,可以导致并发用户几十倍的差距。
--------------------------------------------------------
我不同意你的意见。C++ 的 1ms 和 C# 的 62ms 的比较,62ms 是在单实例运行中,包括 CLR 的装入和初始化时间,以及 C# 的 JIT 编译时间,程序实际运行中业务处理中的时间应该也只有 1ms 左右。如果是在大并发的情况,这些基本开销只能计算一次。例如,并发数为 1,000,000 ,运行时间应该这样计算:
1ms * 1,000,000 + 61ms,而不是:
62ms * 1,000,000

  回复  引用  查看    

#18楼 [楼主] 2008-07-03 12:02 银河      
--引用-(11楼)-------------------------------------------
Mainz:
算法,数据结构,哪个语言都重要;
语言不好就怪用的人算法不好,不公平。
--------------------------------------------------------
同意你的“算法,数据结构,哪个语言都重要”的观点,
不同意你的“语言不好就怪用的人算法不好,不公平”的观点。
1. C# 语言绝对不能说不好。算法不好的话,语言再好也是没有的。
2. C# 语言的运行效率绝对不会差 C/C++ 语言多少,在某些场合还有可能反超。
3. 语言的重要性不仅体现在运行效率,更重要的是体现在开发和维护的效率。
4. C# 毕竟是新生的语言,没有 C/C++ 需要兼容以前的程序的包袱,所以可以采用许多现代的技术和思想,是一个非常优雅的语言。使用 C# 开发程序是非常愉快的。
5. C/C++ 虽然是非常优秀的语言,广泛使用在各种场合。但是,由于历史的原因,有一些不好的地方。例如:
1) class 需要分在 *.h 和 *.cpp 中分别声明和定义,函数也需要先给出原型,然后才能使用。
2) 字符/字符串的种类以及处理它们的函数种类繁多,让人无所适从:
char, wchar_t, TCHAR, WCHAR, LPSTR, LPCSTR, LPWSTR, LPCWSTR, LPTSTR, LPCTSTR, ...
printf, tprintf, wprintf, fopen, tfopen, wfopen, strchar, _mbsrchr, SetWindowTextA, SetWindowTextW, ...
简直太乱了!
  回复  引用  查看    

#19楼  2008-07-03 21:09 zjhjjj [未注册用户]
唉呀。。楼主比错了,C#只能和php,JAVA之类的比,C++很高尚,不要和C++比,不然会玷污了C++的
  回复  引用    

#20楼  2008-07-04 10:32 A.Z! [未注册用户]
分析第一个用例:
10 string[] ss = Console.ReadLine().Split();
11 ulong n = ushort.Parse(ss[0]);
12 ulong a = ushort.Parse(ss[1]);
13 ulong b = ushort.Parse(ss[2]);
14 Console.WriteLine(F(n, a) * F(n, b));


LZ知道这5行代码背后的平台调用吗?如果放弃使用即得类库的实现,会得到很好的性能数据。
同样,Math类的方法比C++的原生调用慢很多也说明了这个问题。


.net的基础性能很好,所谓的基础性能是指完全在托管平台的对象原子操作。
在这个案例中

static ulong F(ulong n, ulong m)
18 {
19 ulong v = 1;
20 for (ulong i = 1; i <= n; i++) v = v * (m + i) / i;
21 return v;
22 }

这三行运算的性能不会比c++差。

  回复  引用    

#21楼  2008-07-04 10:54 最坏是单飞      
新人学习。
我认为哪种语言都有它的优势。
  回复  引用  查看    

#22楼 [楼主] 2008-07-04 11:47 银河      
@A.Z! (20楼)

> 分析第一个用例:
> 10 string[] ss = Console.ReadLine().Split();
> 11 ulong n = ushort.Parse(ss[0]);
> 12 ulong a = ushort.Parse(ss[1]);
> 13 ulong b = ushort.Parse(ss[2]);
> 14 Console.WriteLine(F(n, a) * F(n, b));
> LZ知道这5行代码背后的平台调用吗?如果放弃使用即得类库的实现,会得到很好的性能数据。

我认为这 5 行都很正常,没有什么费时的操作。无论是 Console.ReadLine()、Console.WriteLine(),还是 string.Split() 和 ushort.Parse() 都不是什么效率很低的操作。而且总共才执行 6 次操作(ushort.Parse() 3 次,其余各 1 次),根本用不了多少时间。
我认为一个 C# 程序的 Main() 方法即便是空的,也差不多需要 0.062 秒,这是 CLR 的加载和初始以及 JIT 编译时所需要时间,是基本的开销。


> 同样,Math类的方法比C++的原生调用慢很多也说明了这个问题。

这个我还没有听说过。请问这个“慢很多”的具体数据是多少?能给我具体的资料吗?如果网上有相关的资料的话请给个 URL 给我。谢谢!



  回复  引用  查看    

#23楼  2008-07-04 12:52 A.Z! [未注册用户]
--引用--------------------------------------------------
银河: @A.Z! (20楼)

&gt; 分析第一个用例:
&gt; 10 string[] ss = Console.ReadLine().Split();
&gt; 11 ulong n = ushort.Parse(ss[0]);
&gt; 12 ulong a = ushort.Parse(ss[1]);
&gt; 13 ulong b = ushort.Parse(ss[2]);
&gt; 14 Console.WriteLine(F(n, a) * F(n, b));
&gt; LZ知道这5行代码背后的平台调用吗?如果放弃使用即得类库的实现,会得到很好的性能数据。

我认为这 5 行都很正常,没有什么费时的操作。无论是 Console.ReadLine()、Console.WriteLine(),还是 string.Split() 和 ushort.Parse() 都不是什么效率很低的操作。而且总共才执行 6 次操作(ushort.Parse() 3 次,其余各 1 次),根本用不了多少时间。
我认为一个 C# 程序的 Main() 方法即便是空的,也差不多需要 0.062 秒,这是 CLR 的加载和初始以及 JIT 编译时所需要时间,是基本的开销。


&gt; 同样,Math类的方法比C++的原生调用慢很多也说明了这个问题。

这个我还没有听说过。请问这个“慢很多”的具体数据是多少?能给我具体的资料吗?如果网上有相关的资料的话请给个 URL 给我。谢谢!



--------------------------------------------------------

没有资料可以和你share,一般的效率比较是不会计算上.net程序冷启动时间的。
.net vs java vs c++ vs c,说白了就是运行时和编译器的比较。
但是各自的框架本身就决定了它们会面向不同的领域,现实中少不了交叠的部分,谁有优势是针对这些交集而言的。同样是c,有人在写单片机,有人在写小程序,也有人在写基础库。同样是c++有人写mfc,也有人写系统驱动。.net的基础性能能不能超过C++取决于他的动态优化编译。虽然.net性能上和主流的c++编译器的产物相差不大,但是实际的工程项目中却不会用它来作为开发工具,其中很大的原因是一种偏见。ms在自己的多种平台和多种产品中混合.net进行开发在一定程度上掩饰了这种偏见,不过这个根深蒂固的偏见仍旧会在很长一段时间延续下去。我的看法是lz的贴的代码不足以证明lz的结论,有点像刚毕业或没有毕业的学生写的。
  回复  引用    

#24楼  2008-07-07 09:31 装配脑袋      
某些问题.NET的Emit如果用好,所能达到的综合运行性能就是C++望尘莫及的。
  回复  引用  查看    

#25楼  2008-07-14 20:27 JimLiu      
我觉得——
其实ACM中强调的算法和在应用程序中使用得到的算法还是有区别的。
ACM题目里给了明确的数据范围,所以我们为了节省时间,开数组从来不用动态数组,题目说n最大10000我们就直接开int s[10001]这样。
很明显这在做应用程序中是不现实的,因为如果处处追求这效率的话内存就疯了。
还有些地方也一样,比如
for (int i=0.....){
for (int j=0.....){
}
}
这样j会被外层循环重复地申请和释放,为了追求极致效率的时候我们会把i和j都定义在外面,有时候为了常数级优化(题目卡时间卡得很准的那种)还真得这么干。但对于应用程序开发来说这样的代码就实在太脏了,不应该采用。
但是程序=数据结构+算法——这一点是绝对的硬道理。
那么算法对于应用程序开发的指导意义起在哪里呢?我认为,作用在于——引导我们寻找到正确的优化方向。“性能瓶颈最可能出现在什么地方?”这个问题在当我们对程序执行的算法流程有了一定的了解后,就比盲目地优化代码更容易找到症结所在了。
  回复  引用  查看    

#26楼  2008-07-14 20:31 JimLiu      
--引用--------------------------------------------------
zjhjjj: 唉呀。。楼主比错了,C#只能和php,JAVA之类的比,C++很高尚,不要和C++比,不然会玷污了C++的
--------------------------------------------------------
不是楼主比错了,我觉得是因为比较C#和JAVA的实在太多了,LZ再比就会引发历史遗留问题,呵呵!
  回复  引用  查看    

#27楼 [楼主] 2008-07-14 21:32 银河      
@JimLiu (25楼)

> 其实ACM中强调的算法和在应用程序中使用得到的算法还是有区别的。

同意 JimLiu 的这句话,但是我认为这个区别主要体现在解 ACM 题时可以假定输入的数据会严格按照题目所说明的,也就是遵守契约,有点按照契约编程的意思。而在实际的软件开发中,你就得假定用户的输入可以是千奇百怪的,甚至是故意破坏系统的,必须防止垃圾数据和攻击性的数据,也就是要注意程序的鲁棒性。在实际的软件开发中,按契约编程是指你的程序库可以相信调用她的方法的输入参数遵守约定,而不能假设用户的输入遵守约定。

至少你说的开数组从来不用动态数组,这可能跟个人的编程习惯有关,我在解 ACM 题时从来都是用动态数组的。

还有你举的二重循环的例子,应该说是不对的,编译器是不会让 j 被外层循环重复地申请和释放的。这个二重循环这样写是完全没有问题的,把 i 和 j 都定义在外面,除了污染变量的名字空间外,一点也不会提高效率。

不好意思,说了以上这些。

  回复  引用  查看    

#28楼  2008-07-15 20:25 JimLiu      
@银河
嗯,博主说的很中肯。ACM题目的输入是严格的,不具有任何攻击性的,所以除非题目故意卡输入格式,一般输入都是容易解决的。但实际应用中,输入数据是无法预测的。甚至说是恶意的(比如Injection),所以一边处理输入的同时还要考虑友好地提示用户,有时候会让开发者很郁闷。
至少你说的开数组从来不用动态数组,这可能跟个人的编程习惯有关,我在解 ACM 题时从来都是用动态数组的。
——的确,也许和我是在学习C语言的过程中开始接触ACM有关,C语言中动态申请内存是比较麻烦的(当时看来麻烦,其实现在觉得还是挺容易的,虽然比起C++里直接new和delete就不如了),所以我们周围一群人都统统开大数组。
也有过例外,在做五子棋AI的时候需要保存一个很大的权值表,结果我当时是用了三角形数组,内存从400M省到200M,动态申请内存的优势就体现出来了。
编译器怎么优化我还真没研究过,也许这只是我自己“美好的愿望”吧,呵呵!不过以前用C做ACM的时候就不存在了——遍历都只能定义在顶头,呵呵。
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-07-02 23:19 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: