作者:银河 来源:博客园 酷勤网收集 2008-07-03
最近一段时间,我在 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 重新编译成本地代码。重新编码的代码可能重新组织,根据刚才观察到的执行模式,减少不正确的分支预测。
评论
基于 C/C++ 开发人员普遍功底扎实,而 .NET 开发人员则相对的欠缺,偶对 .NET 在实际开发中的应用程序性能表示担忧。
C#的优势在于快速的开发,稳定的内存管理,这些都是C++无法企及的,牺牲速度换来程序员的省心已经是当今程序开发所追求的目标了,因为大部分程序员都不是负责设计系统核心级或者驱动级的程序的。不然的话,也就不会有Python和Ruby如此流行的现象了。
Angel Lucifer:
基于 C/C++ 开发人员普遍功底扎实,而 .NET 开发人员则相对的欠缺,偶对 .NET 在实际开发中的应用程序性能表示担忧。
--------------------------------------------------------
基本同意你的上述观点。但是:
而 .NET 开发人员则相对的欠缺,这句话应该加个限定词:有些。
因为很多 .NET 开发人员也是(或者曾经是)基于 C/C++ 开发人员。所以说功底相对欠缺的 .NET 开发人员大部分(这应该只占很少比例)是没有学过 C/C++ 的开发人员。
我不但对 .NET 在实际开发中的应用程序性能表示担忧,也对 C/C++ 在实际开发中的应用程序性能表示担忧。实际上,应用程序的性能取决于其算法,取决于开发人员的素质,而不是取决于所使用程序设计语言。
U2U: 算法才是根本
--------------------------------------------------------
非常同意。知音啊。
同样C#也可以写游戏啦、数学运算啦,但C++用起来可能更顺手。
没意义。
以现在电脑的性能提升速度,对于一般的应用程序,尤其是基于web的,0.001秒和0.062秒,对用户来说,能感觉得到区别吗?但是,开发周期是三个月还是一年,就是显著区别了.
大规模并发实时核心交易处理当然是C或者C++
web应用还是Asp.net好用
算法,数据结构,哪个语言都重要;
语言不好就怪用的人算法不好,不公平。
至于C#与C/C++的效率,基本上没有必要进行太过苛刻的对比,毕竟它们所适用的领域不同。当我们使用C#时,就放弃了最高的运行效率,同时换取了不错的开发效率、稳定性和安全性。在非核心应用中,对于开发和使用成本来说,多买一些机器来运行C#,比多花几个月写C/C++合适得多。
而一个糟糕的算法,会让C/C++/C#一起没完没了地耗费掉我们的预算。
大并发的情况下,区别太大。 1ms和68ms的执行时间,可以导致并发用户几十倍的差距。
Rainy: 楼主近来若干算法类文章非常值得阅读,我们在开发过程中往往忽视掉优秀算法的使用。
--------------------------------------------------------
谢谢关注。
在这系列随笔中,虽然有一些算法的最优的,但也有一些算法并不是最优的,只不过能够 Accepted 而已。希望各位园友如果知道有更好的算法可以在该随笔的评论中告诉我。
谢谢!
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
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, ...
简直太乱了!

