分类: C/C++
2014-08-11 17:51:48
Robert C. Martin博客中文版有一篇文章,比较了Java, C/C++和Ruby的性能。
看过之后有些不解,于是作了一些测试。在此写一下我的心得体会。
原文地址:
http://blog.csdn.net/rmartin/archive/2006/08/30/1143161.aspx
我这里没有ruby和Java,因此用C#替代,C#的效率应该与Java相当。
原文有一处Bug,我做了一下修改,并且把计时代码提出到循环外。
为了不打断文章,修改后的代码代码附在本文结尾。
经测试,C++和C#的执行性能极为相近,大约在4.3到4.4秒。C++的
平均性能要略高。
接下来,我们必须要查找到影响性能的关键代码。很明显是这一段:
for (int j=2*n; j
经测试,这段代码至少消耗了90%的时间。
下面比较一下产生的汇编代码:
C++:
00401049 mov byte ptr [eax+esi],0
0040104D add eax,ecx
0040104F cmp eax,edi
00401051 jl generate+49h (401049h)
C#:
000000b4 cmp ebp,dword ptr [esi+4]
000000b7 jb 000000BE
000000b9 call 79313459
000000be mov byte ptr [esi+ebp+8],0
000000c3 add ebp,edi
000000c5 cmp ebp,ebx
000000c7 jl 000000B4
可以看出,C++的代码更加精减。C#由于要进行运行期的检查,要多
产生一些代码。
那么为什么如此高度优化的C++代码却和C#的效率相差无几?初步猜测,
这与处理器高速缓存的命中率有关。
于是针对这个思路,做了新的测试,减小使用内存的尺寸。获得的执行
时间如下:
C++:453ms
C#:625ms
可以看出,性能的差别还是很明显的。
经过多次调整数据,可以发现,内存超过1M的时候,两种语言的效率
就会非常接近。由此可见,优化代码除了考虑代码本身的效率,还要
考虑缓存的问题。
有兴趣的话可以测试一下这两段代码:
条件一的情况:C++:7.5s C#:20.2s
另外一种情况:C++:1.82s C#:2.00s
测试结果可以说明缓存所带来的问题。注意,跟内存分配关系不大的,
即使我把C#的“bool[] p = new bool[i];”提出来,也要耗时14s多。
// C++
int main(int ac, char** av)
{
unsigned time = GetTickCount();
#if 1
for(int i = 100000; i < 200000; i += 1)
#else
for(int i = 1000000; i < 2000000; i += 1000)
#endif
{
bool* p = new bool[i];
for(int j = 0; j < i; j += 2)
{
p[j] = false;
}
delete [] p;
}
time = GetTickCount() - time;
cout << time << endl;
}
// C#
static void Main(string[] args)
{
DateTime start = DateTime.Now;
#if true
for(int i = 100000; i < 200000; i += 1)
#else
for(int i = 1000000; i < 2000000; i += 1000)
#endif
{
bool[] p = new bool[i];
for (int j = 0; j < i; j += 2)
{
p[j] = false;
}
}
TimeSpan c = DateTime.Now - start;
System.Console.WriteLine(c.TotalMilliseconds);
}
在此顺便说一下我对C++和C#的看法。
首先,必须强调,两种语言都是非常优秀的,并且都有其优势领域。
其次,C#效率很高,用得好的话,的确有可能超过C++程序的效率。有兴趣
的话,大家可以比较一下Apache的xerces xml和.net 2.0的xml,经我测试,
.net 2.0的效率要高一些。
再次,C/C++的效率是最高的(C和C++的代码效率没什么差别,看看生成
的汇编代码就知道了),但是也要看你怎么写程序。你可以写出效率很差
的程序,比如TinyXml,但是也有可能由此获得一定的好处(TinyXml真的
很小)。你也可以写出效率非常高的程序,我曾经写的一个xml解析器效率
是.net 2.0的两倍多,不过也因此去掉了一些xml中不常用的特性。
下面比较一下C#和C++的不同,借此再讨论一下效率的差别。
基于.net framework的程序都有类似的特点,因此我们可以用C#作为其
代表语言来讨论。
C#有以下几点有别于C++的地方:
1:JIT。C#或者其它的.net语言都会被编译成MSIL,运行时才会被编译成
本地代码。这会导致两个坏处:启动速度慢,和优化不完全。不过也有可
能获得一定的好处,比如针对处理器进行特殊的优化,抛弃不用的代码以
减少内存使用等。总的来说,JIT是会降低效率的。微软提供了ngen来生成
本地代码,效率提升很明显,尤其是启动速度和内存用量上的变化。
2:垃圾收集。GC的分配速度很快,这一点要比C++的缺省的new高效。
由于有GC存在,无需维护引用计数,资源的共享变得很容易,这也会简化
代码和提高效率。
不过Collect的时候还是会耗费一些时间。而且大量垃圾内存驻留着,使得
内存用量居高不下。无论是Java还是C#程序,内存用量都比C++要多。
C++可以使用内存池来优化内存的分配,效率会有很大提高。未经优化
的C++程序有可能在这方面极其低效,尤其是某些滥用STL的程序。不了解
底层的实现而去随意使用STL容器会在无意之中就耗尽了CPU资源,隐含的
拷贝构造多数时候就是罪魁祸首。C#和Java就没有这方面的问题。
3:RTTI。强大的RTTI可以使我们很容易地设计某些功能,但是注定会丧失
一些效率。C++里,多数时候使用static_cast就可以解决问题。即使是需要
动态的地方,也可以有一些特殊的解决办法。RTTI和效率就是鱼与熊掌,看
你如何权衡了。
4:指针。C#程序不常用指针,虽然可以使用unsafe代码,但是仔细推敲一
下它生成的最终代码,会发现还不如不用。C#的指针的最大作用应该是使用
这个东西来调用C/C++的函数。
灵活使用指针可以减少数据的拷贝,可以生成效率非常高的代码,也可以
简化代码。C#里必须要使用Marshal来实现这些功能,其实挺麻烦,也低效。
5:Runtime Check。这个就不用多说了,本文所附的程序就是活例子。
Runtime Check与效率也是鱼与熊掌,需要权衡。
总之,使用什么语言,还要看你打算做什么东西。我们必须要客观地了解
各种语言的优缺点,才能做出正确的选择。
// C++
#include
#include
#include
using namespace std;
void generate(int max)
{
bool *sieve = new bool[max];
memset(sieve, 1, max*(sizeof(bool)));
sieve[0] = false;
sieve[1] = false;
double maxsqrt = sqrt((double)max);
for (int n=2; n
if (sieve[n])
{
for (int j=2*n; j
}
}
delete[] sieve;
}
int main(int ac, char** av)
{
unsigned time = GetTickCount();
#if 0
//for (int i = 1000000; i < 2000000; i += 10000)
for (int i = 10000; i < 20000; i += 1)
{
generate(i);
}
#else
for (int i=100000; i<=5000000; i+=100000)
{
generate(i);
}
#endif
time = GetTickCount() - time;
cout << time << endl;
}
// C#
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
public static unsafe void generate(int max)
{
Boolean[] sieve;
sieve = new Boolean[max];
for (int i = 0; i < max; i++)
sieve[i] = true;
sieve[0] = false;
sieve[1] = false;
int maxsqrt = (int)Math.Sqrt(max);
for (int i = 2; i < maxsqrt; i++)
{
if (sieve[i])
{
for (int j = 2 * i; j < max; j += i)
{
sieve[j] = false;
}
}
}
}
static void Main(string[] args)
{
DateTime start = DateTime.Now;
#if false
//for (int i = 1000000; i < 2000000; i += 10000)
for (int i = 10000; i < 20000; i += 1)
{
generate(i);
}
#else
for (int i = 100000; i <= 5000000; i += 100000)
{
generate(i);
}
#endif
TimeSpan c = DateTime.Now - start;
System.Console.WriteLine(c.TotalMilliseconds);
}
}
}