看到一篇好文章,不轉的話,對不起大家,呵呵,都來看看吧……

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<max; j+=n)
sieve[j] = false;
經測試,這段代碼至少消耗了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 <iostream>
#include <math.h>
#include <Windows.h>
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<maxsqrt; n++)
{
if (sieve[n])
{
for (int j=2*n; j<max; j+=n)
sieve[j] = false;
}
}
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);
}
}
}