天天看點

C ,C# 效率研究

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

C ,C# 效率研究

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);

        }

    }

}