<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Sung in Blog

               一些技術文章 & 一些生活雜碎

    在本書的第一部分,我講述了C#語言的許多基本方面——包括整型、字符型和浮點型之間的轉換,循環、迭代算法,以及字符串比較、拼接和 特定符號統計 。正如我所說,對一種新的語言與它的類似語言進行全面、公平的性能比較,范圍將會很龐大,所以本書沿用第一個例子并只對有限的重要的部分特性做測試。這種比較,以先前的 C#及它的庫與C、C++、D和Java的特性對比為基礎,主要在以下幾個領域:

    ·類機制(封裝與模板)

    ·異常

    ·內存— 重點

    ·大型文件的隨機訪問

    ·資源獲取初始化

    正如我在第一部分提到的,這里所做的測試只是針對程序性能的一小部分,硬件及操作系統的配置是測試語言 /編譯器/庫函數的機器的典型配置:2-GHZ,512-MB,Pentium IV,Windows XP Professional。 這里我們看到的任何結果揭示了在其它環境下可能的性能,這不說明可以假設這些結果就是正確的典型例子。 最后要說明的是,當我提到“C#比Java好”等結論的時候,一定是指“在特定的測試環境的條件下”,這一點請你銘記在心。

    性能評價

    所有的代碼還是用和第一部分一樣的編譯器編譯。 C#代碼在.NET框架 1.0.3705(可從http://microsoft.com/netframework/免費下載)下,由Visual C#.NET 編譯器v7.00.9466編譯。D代碼由Digital Mars D 編譯器 Alpha v0.62 (可從http://www.digitalmars.com/免費下載) 編譯。Java代碼由J2DKSE1.4.1-02 ( 可免費下載 http://java.sun.com/ ) 編譯。 C和C++代碼 ( 單線程 , 包括 raii , 和靜態鏈接庫 ) 由使用 STLPort v4.5的Digital Mars C/C++ v8.33 ( 可從 http://www.digitalmars.com/免費下載 ) 和使用 Visual C++ 6.0的頭文件和庫文件的Intel C/C++ v7.0來編譯。所有這些編譯器均使用最優化處理 ( set for speed ) ; Intel 用-QaxW flag [2]提供兩套編碼路徑:一個是針對有SSE2的Pentium IV處理器,另一個是針對其他所有的處理器——在運行時選擇合適的一個。(因為Intel編譯器只適用于Intel處理器構造,特別是在這個測試中的Pentium IV,這編譯器的編譯結果必須在此基礎上評價,不能在其他的處理器上進行同樣的過程,比如Athlon,甚至是舊版本的Intel處理器也不行。)

    后面將要描述我們測試的假想的五種例子中的性能表現。他們都用2-GHZ,512-MB,Pentium IV,Windows XP Professional的配置,并沿用在第一部分用的性能分析系統。每一種語言所編程序的調試都執行7次,同時沒有其他占線過程,去掉最高速的和最低速的,紀錄平均值。所有的時間信息,通過僅區分相應時間區間的高性能計時器保存下來。(C用戶調用Win32 API QueryPerformanceCounter() ;C++ 用 Win-STL ' s performance_counter [4], 可在http://winstl.org/找到 ; C# 用 SynSoft.Performance.PerformanceCounter ,可在 http://synsoft.org/dotnet.html找到; D 用 synsoft.win32.perf.PerformanceCounter , 可在http://synsoft.org/d.html找到;Java 用 System.currentTimeMillis() .)每一個測試程序都包含一個啟動迭代的程序,這樣可以將啟動或緩沖時的影響最小化。

    在每一個例子中的C#,C++和D的特征: C在所有 保存 封裝和 raii( 類)的地方出現,因為它沒有這兩種例子必需的語言特征。 raii 沒有出現Java,因為Java甚至連 Resource AcquisitionIs Initialization (RAII [1])的機制也沒有,所以它僅僅作為在GC-kicking的一個練習。簡單的說,我不會展示所有的語言的每一個例子的所有特性,但是所有這些都包含于在線記錄中(連同產生他們的Digital Mars make-compatible make-file)。

    下面就對測試性能的例子進行評價 :


    封裝 。這個例子( Listing1)的目的是比較不同語言(除了C)在類機制上的代價,這些語言并不完全支持相對于它們所做的所有類型的一般處理。尤其是C#和Java一般不能存儲和使用它們的內置類型實例,比如int,long,bool等等,同樣,對于從這些實例派生的類型也不行。相反C++和D都支持模板,它可以給任何類型的一般性操作帶來方便。在以后的文章里,我將詳細地講解容器。但在這個章節將不會用到它,如果提及它反而會分散對于封裝意義的理解。C#和Java中未封裝的變量傳遞ints給異或函數,封裝變量傳遞整形值在Int32(C#)和Interger(Java)的實例中。注意Java不支持模糊的封裝(這被證明是好的因為編譯器將對于這種封裝帶來的低效給予警告),所以實例必須明確地被創建(在列表1中標記“Java ” 處可以看到)。 C++和D的運行是簡單的使用模板,這就是它們的泛型機制。(在這里我不對“一切都是對象”的模板的好處作深入探討,但是我將在這期的姊妹篇Stepanov camp中對它做詳細講解)。

    異常處理 。異常的語義在各種語言之間有很大的區別。 在 C++,異常處理和調用析構函數一起沿著堆棧操作下來,然而在有垃圾回收的語言中,這些對象被丟棄,直到將來的某一時刻被收集。 這樣,就無法對各種語言進行有意義的比較。所以在這個例子中,只能通過將一段 using/aware代碼與另一段沒有異常處理支持的相同代碼進行比較。這個例子有三種測試,它們的目的就是去衡量使用和拋出異常處理的代價。第一(Listing 2)是比較使用異常處理和通過函數返回值來傳遞信息給調用者這兩者的代價。第二(Listing 3)是評估在try-catch模塊中執行的代價(盡管沒有異常處理被拋出)。最后(Listing 4)就是評估遍歷執行try-catch模塊的代價(同樣,這沒有異常處理被拋出)。




    Mstress。 這一個例子的目的是,在假設分配比回收優越的基礎上,看看垃圾回收語言是否在高的內存利用率上有重要的非線形關系。這里存在四種測試,每一個都牽涉到分配與釋放( C 和 C++)或者丟棄(C#, D, 和 Java)在一個時間段內的內存塊。這些塊大小是通過相同的偽隨機數算法生成的(詳見文章的第一部分),這能使塊的大小處于1至1000之間,從而在確保每種語言在相同的環境下測試時仿效現實世界中的分配。第一種測試(Listing 5)通過一個不斷循環的變量分配了1000個內存塊,目的是看看頻繁地分配會不會“留下”更多的碎片,這些碎片會阻礙分配的進行直到突然產生明顯的GC收集。第二種測試(Listing 5)增加了1000次循環中分配的次數,以測試由不斷增加的內存負擔引起的性能的 損失 。 第三和第四種測試(沒列出來)跟前兩個相似,它們增加了另外的內存塊和分配/回收的交叉活動,目的是為了模擬不斷增加的內存碎片。 C使用malloc()/free(),而C++,C#,D和JAVA用new[]。


    rafile 。這一個例子( Listing 6)評估各種語言從大文件中隨機讀取的能力。它利用某種偽隨機算法(詳見文章第一部分)來模擬在大文件中的查找/讀取。這里用到了兩個文件,一個4.2MB,一個21.1MB。在每一個查找點,四個字節被讀取,并且和一個運行值進行異或運算(這樣有利于證明所有的語言以相同的方式查找和讀取)。有意思的是,原始的運行程序讀了32位的整數,但不幸的是,JAVA用了network byte-order而不是host byte-order,這與C, C++, 和D的machine-order reading是不相容的。


    Raii(類) : 這最后的例子目的是為了評估沒有內建支持這個非常重要的語言特征(很不幸 C#和Java都沒有)而付出的代價。此例子表達了通過資源句柄XUser 類型從XFactory資源管理器分配X資源到客戶端代碼(見代碼段7)。在C++和D語言中,XUser 提供一個析構函數,在析構函數里分配的資源自動地回收到資源庫,資源在這種情況下是個信號量,.NET和D的執行代碼可從 http://synsoft.org/dotnet.html http://synsoft.org/d.html 下載。C#、D和Java 提供它們自己抽象的同步機制,但我需要一個信號量去引起交叉線程的競爭,去保證同樣的同步機制以便所有語言的進行生動的比較。此信號量在每種語言執行代碼中都有最初可獲得的五個鍵值。


    盡管 C#的XUser 類有一個析構函數,但此析構函數僅在實例進行垃圾碎片收集時被調用。(C#使用析構函數的語法,但編譯器把它翻譯成 CLR (公共語言運行時刻)的 Finalize() 方法。)這種情況跟 Java一樣。C# 還盡量更多地支持習慣用語(雖然它支持的還不夠),這是通過使用引用聲明來實現的,正如表7所示。(為了方便,“using-ed”類型必須實現 IDisposable 接口,否則聲明無效),一個明顯的問題是類的使用者必須承擔確保語義的正確的負擔,而不是類的開發者。這是奇怪的二分法:C#是面向對象的,但它依靠類的使用者對類的內部執行代碼的足夠理解以決定是否需要此類的直接析構。C++和D語言過程明確,由類自己決定:C++通過析構函數的執行,D提供析構或終止函數,類聲明驗證關鍵字 AUTO (沒有這些,它按C#和Java的方式運行正常的垃圾碎片收集程序)。

    C#語言使用 USING 聲明(和 IDisposable 接口)的一個可替代方法是依靠垃圾回收。在形成這個情況之前,我假設當內存使用率達到觸發閥值或者主線程空閑/等待的時候啟動垃圾碎片回收。(我不知道這種誤解是怎么形成的,或許只是異想天開)。當我在執行 Raii2 例子的最初版本的時候(變量沒有使用 USING 聲明),處理進程馬上掛起。很明顯,在其他線程堵塞時垃圾碎片收集沒有觸動,只有在內存消耗到一定程度才觸動。因此,為了程序執行,我們被迫在另一個工作線程中自己剔除垃圾碎片收集。當然,這在實際編程中是非常可笑的執行策略,執行結果證明了這一點。

    結論

    大部分的結果是以絕對運行時間數(全部循環或每循環)或者 C#的執行時間和C,C++,D,Java分別的百分比來表達的。在后一種方式,圍繞著百分刻度的結果表示C#的性能比率,高的值表示C#的性能優越,低的值表示相對低下。 Raii 的情況是個例外,在這里結果是以C++(Digital Mars)時間的百分比表示的,由于垃圾碎片收集代碼的消耗,調用時間值是以對數刻度表示的。這說明在對數比例上歸根于非常高的垃圾處理花費.


    BOX . 從圖 1中我們能看出,在合理范圍里(C#語言的97%-271%)對于所有編譯語言解除封裝操作(就簡單整形數據)成本的差別:最差的是C++(Digital Mars),成本是C#的271%,最好的是C++(Intel) 成本是C#的97%.假設執行相對比較簡單的 異或 運算, C#幾乎可以像 Intel那樣出色,你對這個并不會感到很驚奇,但你相信C#要比C++(Digital Mars)和D模板實例快兩倍.我有些驚訝C++(Digital Mars)這個相對弱的性能,特別是和D比起來它要慢得多,然而他們出自同一開發商。

    顯然這個封裝花費是有意義的 ,相當于C++模板花費4-10倍.這個測試僅僅包括整數,相對來說可能不能完全反映特殊類型.雖然如此,我們能有把握斷言這個模板輕易贏得了性能戰.有趣的是就他們各自的成熟度而言,C#要比java稍好,

    差距并不大 (小于4%).(我想可能由于.NET處理目標JIT3。【3】)。更有趣的是,事實上在未封裝的形態下,c#明顯比java快.


    除了圖 1以外,有人認為,不拋出的異常variants都是是比拋出的異常副本要快的(如圖2). 用 c和c++,Intel完成任務要比所有其它不拋出的異常variants快.c#緊隨其后。似乎c,c++,D(不考慮編譯器)在異常處理上花費大致相等,符合win32異常處理機制的預期限制因素.我們能看到三種語言在拋出異常variants上的差別是比較小的,很可能的原因是由于有相關的額外的管理消耗關系 (c++規定執行破壞功能的堆棧是從捕獲點到拋出點的來回).

    我發現有趣的是在拋出異常variants方面c#和java的執行是有關系的.

    因為這兩種語言語義在異常拋出處理要勝于返回當前錯誤值 ,也勝于其它語言,

    尤其 c語言所呈現的關系執行調用.我非常贊同Brian Kernighan和Rob Pike

    (<>,Addison Wesley,1999)所說的例外僅僅在例外條件的使用,并不是必然會發生的.然而人們能容易看到為何服務器在處理巨大數量的數據的時候可能出現的例外,而經常擔心性能的問題.正是如此,java以小于其它語言25%的消耗給人深刻的印象.由此我們限定win32,采用兩個有代表性的差不多的c編譯器來構造例外處理,雖然沒有定論,但是我認為java使用了不同的機制.

    那么c#的異常拋出性能如何呢?在相關條件下,相對其他語言而言,使用異常處理語句是不使用異常處理語句的21-77倍,而c#則是191倍;鑒于Java的出色表現,.net工具應該引起注意了。

    Except-2. 這個圖表是為了弄清楚在一個 try-catch范圍內異常處理語句的執行是否耗時,如圖3,大多數語言的表現同預期的一樣:c、c++和D對于Digital Mars是類似的;Intel c 和c++比其它都好.c#要比java快.相比較就性能而言,

    c#是很優勢的,令人感興趣的是,c#在進行異常處理是耗時比不進行時還要少.產生這種結果肯定是有原因的(這個結果是經過了多次驗證的), 但由于對于c#異常處理機制我沒有很深的認識,我不能做出合理的解釋.同樣,雖然并沒有定論,對于D來說,是否進行異常處理對結果似乎并無影響!.
    我認為這是因為它不用清除棧內數據, java也有大致相同的自由度,并且表現出的差異也并不大。


    except-3 如圖表 4所示,測試結果與預期大體相符。除了D編譯器的結果中微不足道的差異,所有的語言在遍歷異常處理語句的時候都比不進行是耗時要長,盡管如此,我仍然對在這兩種情況下差異如此之小感到吃驚。我們通常認為使用異常處理機制會耗費時間,并且我認為遍歷異常處理語句會在啟動和完成上耗費大量時間;事實上,我一開始就預期兩者的差異將會非常地顯著。然而,在這個測試當中,兩者的差異相當不明顯。

    但是這個結果并不難理解,導致差異如此小的原因首先在于:這次測試太過于純粹。這次測試的所有異常處理(類型)都是 int型的。盡管并沒有異常被拋出,這樣做的目的在于避免在異常處理體的構造(和析構)上耗費時間和消除各編譯器處理機制不同所造成的影響。其次,本次測試中沒有基于框架的結構體,因為在C++中,盡管析構函數并不會真正被調用,析構函數調用方面的準備也是必須進行的,這樣也會造成時間的耗費。(注意析構函數無論異常是否被拋出都會被調用。)這些耗費都會造成測試的不公平,所以我們選擇了int型。注意到程序不論是因為異常拋出而中止還是正常退出,析構函數都是要被調用的。所以它僅僅做為勾子而被添加進這些額外的函數調用中。然而,我認為所有的這些因素并不是很充足,它們僅僅使我們可以從那張表里知道當不使用的時候異常的開銷是非常小的。很自然,這也正確的表明了一個人對所有語言性能(perform)的期望,當然我們要給它足夠的信任.

    metress-1 這個測試很清楚的表明了在不使用內存分配機制的展示 (exhibit)中不斷增長的對固定數量的內存快(隨機大小)分配/釋放的循環的反應是非線性的。事實上,在很大程度上它們從沒有線性(增長)的趨勢:從性能上來說并沒有什么變化(見圖5),除了在一些低循環(low interations)上有所不同之外.(有趣的是,那些低循環的非線性是有意義的--占到了全部命令的50%還多--當然這僅僅是對c#和java而言).不管內存分配機制是否在每次特循環結束后都立即恢復對1000個內存塊的釋放,或者僅是簡單的把它們交給GC在以后的某個時間處理,語言/運行庫總是看上去幾乎完全不受這些循環執行的影響.

    在這組性能相對的測試中,我們可以清楚的看到一些它們之間的不同。在使用 Digital Mars 分配方式的語言中,C和C++的表現是最好的。Visual C++的運行庫比使用Digital Mars的語言低大概2.5-3倍,對于使用Digital Mars 的語言和Visaul C++運行庫來說,C和C++基本上是相同的。最后,很明顯,Java 比C#慢了3-4倍,而比C慢了差不多7倍.


    mestress -2

    就像我們從圖表中看到的,在一定數目的分配釋放內存的循環中內存塊(隨機大小)的不斷增長的反應是變量的引用是非線性表現的。這正是我們所希望的,因為在每次循環中分配的內存總量是按指數方式增長.每次循環所分配的內存的大小都低于10,000塊.使用Digital Mars分配方式的C 和C++的效率依然是極為優秀的。只是效率表現上低于平均值,而這里也不得不提到java,它的表現同樣不好.Visual C++的運行庫(C和C++都適用)相對于C#,D 和Java來說,有一個不錯的開始,但它很快就降到了一個很低的水平上。

    Java在每次循環是使用10,000內存塊之前的表現非常有競爭力,并在這一點急劇上升。D在整個測試中都幾乎有著一致表現.幾乎一直都是非線性的,這與C#很接近。

    如果可能的話,我都希望看到一條總夠長的X軸,C#在內存溢出的情況下仍然保持每次循環不超出10,000內存塊的水平并預防這種情況的出現。(D和Java似乎也做的到,但那也只是類似C#的行為一旦被發現就中止測試。)

    mstress-3 和 4 從前兩個測試(variants)看,除了時間的的增長,反復的交叉存取和塊變量在執行上的表現并沒有什么不同。曲線的走勢或者說不同的語言/運行庫的相對表現在這一點上沒有什么明顯的改變。

    我認為C和C++,在使用外部free/delete的條件下,重新使用的新近釋放的碎片。相反的,我很難想像出C#,D和Java 是如何使用垃圾收集機制周期性尋找每次循環所分配的內存從而盡可能的減少由內存碎片所引起的負面效應的,或者在這個跟本就沒有產生碎片.除去這些不同,這兩種方式的表現還是很相似的。

    這只是一種理想的我們所希望的分配機制的表現 ---畢竟,那是一個很極端情況,所有內存分配都可以在給定的周期內完全返回---雖然程序的執行都達到的目的。

    rafile. 這次測試中我所希望是那些語言實際 (效率)上并沒有什么不同.除了C++的執行也許比其它的低上幾個百分點.


    圖表 7中可以看出,C#的在文件的隨機存取上比C(使用Digital Mars)要好,但低于C++(使用Intel和VC6的連接庫),和D與Java現表現基本持平。但是C++運行庫表現出令印象深刻的性能。

    從所有這一系列測試來看,Intel 似乎已經能夠產生性能不錯的代碼了。但是它的連接的運行庫頭文件卻是Visual C++ 6.0的,這個(大概)不是由Intel編譯器產生的。因比它幾乎是以壓倒性的性能優勢超過了DMC,這主要是由于各自的Digital Mars 和微軟Visual C++運行庫的性能。(我承認有點吃驚,對于些測試結果正好可以反對兩個賣家的名聲--應該或是不應該得到的)。這也表明一個人的偏見是非常沒有理由的。

    另一件值的注意的事就是對不同大小文件訪問的開銷都非常的小。這也表明所有的語言都利用操作系統的優勢很輕易的達到了相同的程度。


    raII . 從圖 8中我們能看出使用statement的C#的表現只有C++析構函數效果的一半。D的性能與之相當,雖然產生的代碼中有錯誤(或者是D的Phobos運行庫),當進程創建的對像超過32,000左右的時候會使該進程掛起,從而防止過多(一個以上的)通訊中的數據點被確定的使用.在這組單獨的測試中我們可以看到RAII對C#和D的支持是很完善的,但并不如想象中那樣優秀。如有你要做很多scoping--并且你想,也許要足夠的robustaness的幫助---你最好還是選擇(stay with)C++。當依賴于.NET的垃圾回收機制時,因為處理過程只是簡單地被掛起,所以C#的性能比較難以測算。可是我們不希望.NET經常調用它的垃圾回收線程,但我找不到理由解釋為什么回收線程不以較低優先級在內核中等待,當有垃圾對象需要被回收和主線程空閑時再進行有效的回收。當然,我在垃圾回收方面不是專家,并且可能有一種合理的理論原因說明這為什么不是一個好的處理方法。

    GC(垃圾回收)即使響應的更靈敏,我們可以在它的性能結果上看出效果并不與之匹配。對應第二組數據指針的方法表明了GC每隔1ms被觸發一次。

    GC(垃圾回收)的方法比使用使用聲明慢了近千倍,所以這不僅僅是一個技術問題的范圍。自然,這跟關于這個例子的假設有關,同時跟幾乎每個人都用垃圾回收機制去管理頻繁爭用的資源的釋放這個不辯的事實有關,但我沒預計到這個變量的性能是如此之差。同時這也給了讀過Java/.NET書籍的C++程序員對于書籍建議(或許說依靠)使用終止函數來清除資源而感覺疑惑的一個解釋。

    結論

    在第一篇文章,我對于不同的環節得出不同的結論,這些環節或是語言本身造成的,或是庫或者二者造成的,在這里我也這么區分,因為語言特征而產生的影響的部分通常涉及:異常,封裝 /模板和RAII的實現。文件存取部分可以看作直接對庫函數的操作。(沒有任何一種語言阻止寫替換操作,雖然這樣做并不是很容易)。內存管理部分受語言和內存的影響――雖然我不清楚可以用哪一種管理機制去替代C#和JAVA的缺省的內存管理機制,但是對于其他語言這是很簡單的。

    封裝。當我們比較封裝和模板時,我們可以看出模板明顯比封裝出色很多。我相信這不會讓大家感到很驚奇,但是事實上封裝消耗的系統資源是模板的十倍。自然,作為模板類庫的作者( http://stlsoft.org/ ),我得出這個結論也許帶有偏見,但是這些數據在表示更廣泛的情況時,他們自身就說明這一點。在后面,我將說到容器和算法,我們將看到比預計更多的這樣的結果。

    至于有關異常情況,我想講四點:

    1.在所有的程序語言中,使用異常處理從調用的函數過程中返回,而不是保留他們作為異常事件的指示,將導致執行花費巨大的成本。

    2.C#的異常處理機制相對于其他被測試的語言效率是極低的.

    3.在有異常處理的上下文環境下運行(比如用try-catch的情況)對于性能沒有多大影響,除了C#,它為了提高性能(實際上,我對于這種結果很不理解,并且懷疑這是.NET運行期的一個人為結果而不是C#/.NET的一般特征。)

    4.交叉于異常上下文的執行(比如說進入try-catch和/或 離開try-catch)對于系統性能的影響基本上是非常小的。

    Raii. C#支持的RAII例子比C++在性能方面差,雖然不是很多,但與D相比差別無幾,基本一致。(這種一致指出了在處理堆分配對象的確定性析構時的基本限制)然而,從理論觀點,或從易用性和/或穩健性的實踐觀點來看,這里還是有很大差距的。C語言缺乏機制可以解釋為年代已久,并且它是一種程序語言。很遺憾,java缺乏這種機制,但是它只是可以解釋為忽視了。(至今為止我們已經用java8年左右了,所以“忽視”可能也有些牽強。)C#在這方面的努力還不成熟(因為它更多地依賴類的用戶而不是類的作者),很奇怪的是C#/.NET很多優點都是在Java中被看作瑕疵/遺漏的地方,比如屬性,輸出參數,和無符型。

    Mstress. 這個內存測試的目的是證明如果頻繁的內存分配加上垃圾回收機制是否會導致—— C#, D, 和Java這些包含垃圾回收的語言嚴重的非線形,這明顯沒有。這個結果可以看出所有的語言/庫表現的非常合理的。這里我們可得出幾個有趣的結論:

    1.C語言和C++語言,提供正確的庫支持,他們內存分配空間最快。毫無疑問,部分原因由于它們沒有初始化字符數組的內容。根據結果,我重新對C語言進行測試,用calloc()替代malloc(),測試結果很接近C#,雖然仍然會高出5個百分點。

    2.內存碎片(只要輪流存取第三和第四個變量)不會在很大程度上影響內存分配的性能,不會增加總體負擔。

    3.如果垃圾回收器安排在沒有使用的內存區之后不執行(我假設這是可以的),這將不會對性能有太大的影響。我假設,這說明了C#是第一個會內存枯竭的語言,所以我們可以假定它們通過使用大量的內存空間在內存分配性能方便取得平衡,并且相信在現實的環境中有這種機會運行垃圾回收。

    4.通常更希望體面地降低性能―就象C(Digital Mars)的方式――而不是在各個方面都有很強大的性能,然后在某些未知的閥值化為烏有:在服務器環境下,某一時刻提供一個慢點的服務比導致崩潰可能要好。由于對Digital Mars和Visual C++,C和C++的運行實際上是相同的,我們可以假定因為與通過new操作符的調用的交流而增加的成本可以忽略,并且在C和C++之間沒有本質的區別。

    5.C#內存分配時間會比Java快上3~4倍。

    總的來說,這個結果并不象我想象的那樣:我期望C和C++在中小應用比 C#,D,和Java稍微落后,但在大型應用中遠遠優于。真是學無止境。

    Rafile . 對于文件的隨機存儲結果可以看出一些重點。我認為最重要的一點是仔細的選擇庫。之所以C++的運行效果看上去要比C和其他的語言好很多,就是因為庫的原因。當把C++的性能(Intel 和VC6庫)和其他語言/編譯器進行比較時,用其他任何一種都很給人留下深刻的印象。自然,這邊的例子是故意的,但這么大的性能差別的事實——比C#和 Java快23倍,我們可以期待在現實情況中性能的有意義的差別。(這里,再次證明了偏見是錯誤的:我很厭惡所有的io流,從很多方面很容易能看出它的效率很低,當然也不能說全部這樣。我對于這樣的性能印象很深刻)。

    詳細的總結了C#的性能以后,我想就我們在其他語言方面的研究發現做一個簡短的介紹。

    1.C處理異常事件是不錯的,特別是在存儲和文件(提供正確的庫連接)有非常好的表現;它能提供所有我們能預期的效果,但是與后來的高級語言相比,吸引不了更多的學習者。

    2.C++處理異常事件也不錯,特別是在存儲和文件(提供正確的庫連接)有非常好的表現,而且包含模塊的概念和有效的RAII;它始終得到我的鐘愛,我認為它在未來的很長一段時間內都是編程的最佳選擇。

    3.D在異常事件處理上不是很太好,在文件處理上處于一般水平,在存儲方面有比較合理的相關性能和非常不錯的線性度,有模塊和RAII(雖然語言本身有很多的bug產生,使得很多的處理過程被掛起);當這語言經過它的初級階段,期望會得到不錯的效果。

    4.Java在封裝和內存管理的性能上表現最差,不過它有好的異常事件處理,但沒有一點模塊和RAII的概念;它不指望會達到真正的美好,我認為C#/.NET可以衡量出它的高低,至少運行在Windows平臺上 。

    摘要

    從第一部分開始,就給出全文的延伸的意思,從性能方面,如何能更好的用C#/.NET寫出很好的軟件。在第一部分里,一些結果雖然不是很值得注意,但是有很多的地方還是令人叫奇的(至少我是這樣的!)。

    這些研究結果展示了C#可以提供非常好的性能效果——至少在我們測試的范圍內是這樣——同時不能把它如同象過去把Visual Basic和一些擴展版本的Java當作一種性能差的語言來對待。對我而言,我比較注意語言運行的性能,當然,他們的表現也會超出我的預期效果。我所提出的嚴肅評論是從語言特征的觀點和對多個編程范例的支持方面得出的。

    作為一個最初使用C++的程序員,我從特定的觀點得出這些比較結果。這可以從RAII和封裝測試以及它們的語言解釋上看的出來。在某種意義上來說,這就好比比較蘋果和橙子,對于不同背景的人們可能會對我為什么如此強調模板和確定性析構感到疑惑,沒有這些他們也進行的很好。

    毫無疑問,模板給C++帶來了非凡的革命,它能支持群組合作,或者獨立運行,范例是無可比擬的。Java由于缺少模板和強制要求任何東西都是對象,而被批評了很長時間。.NET框架在這方面做法一樣也很讓人失望;可能由于Java缺少模板而他們可以在.NET環境下得到(他們確實達到)讓更廣的開發團體感到信賴并接受了它。

    缺少對RAII的支持對GUIs(通用用戶接口)是非常好的,這是在嚴格的框架內操作的軟件——即使象J2EE這樣精密復雜和高吞吐量的語言,和非關鍵的程序。但復雜的軟件不必要因為它而復雜。在最好的情況下,到處都是最終模塊,只要在Finalize()里函數里添加Close()方法就可以了。在最壞的情況下,滯緩或者隨機的資源泄漏都會導致系統的崩潰。更甚于以上的,如果OO(面向對象)——就象C#和Java所有都是對象——是你的目的那更讓我不安,第一個要失去的就是對象自己清除自己的責任,我沒辦法理解這個。(我知道一個非常出名的C++程序員——當然我不能告訴是誰,他告訴我當他在課程中突出RAII和模板的重點時,那些非使用C++的人們露出的滑稽表情,讓他感覺好象他丟失了什么東西)

    D是使用垃圾回收的語言,默認是非確定性析構,有很多地方與C#和Java相似,但是盡管如此,它還是兼容支持RAII的習慣同時具有模板。并且它是一個人寫的!我不明白為什么我們對Java或者C#(.NET其它語言也是一樣)印象如此深刻,即使有它們支持RAII和模板的缺點,而我們又能比較這些。有可能C#/.NET成功的原因和Java一樣,有大量的,有用的庫文件(C和C++應該從中學習,D應該去發展),和有一個強有力的后臺。

    最后,我想說對于所有的性能結果的比較分析,你必須明智地使用這些結果。我努力使自己公平,選擇我相信是公平和有意義的測試。但你一定要判斷的出這些例子僅代表了廣大可能性的的一小部分(不是無限意義上的),它們不是實際的程序,僅僅是測試而已并且把它簡化了,其中不可避免的帶有我個人的某種偏見在里面。通過逐步的方法,我希望降低這些因素。但你在閱讀和使用這些結果的時候要保持留意。

    感謝

    我要感謝 Walter Bright提供給我最新的Dv0.62版本,能夠完全的測試異常事件。感謝Intel的David Hackson給我提供了C/C++編譯器。還要感謝Scott Patterson幫我選擇了切合實際的測試方法(他總是不斷的在我煩躁、偏題、愚蠢的時候提醒我)。還要感謝Eugene Gershnik 對于我的一些斷言給了我嚴厲的反駁,幫助我適當地注意一些防止誤解的說明。

    Notes and References

    [1] The C++ Programming Language, Bjarne Stroustrup, Addison Wesley, 1997

    [2] The Software Optimization Cookbook, Richard Gerber, Intel Press, 2002

    [3] Applied Microsoft .NET Framework Programming 1 st Edition, Jeffrey Richter, Microsoft Press, 2002

    [4] Described in “ Win32 Performance Measurement Options, ” Matthew Wilson, Windows Developer Network, Volume 2 Number 5, May 2003.

    在本書的第一部分,我講述了C#語言的許多基本方面——包括整型、字符型和浮點型之間的轉換,循環、迭代算法,以及字符串比較、拼接和 特定符號統計 。正如我所說,對一種新的語言與它的類似語言進行全面、公平的性能比較,范圍將會很龐大,所以本書沿用第一個例子并只對有限的重要的部分特性做測試。這種比較,以先前的 C#及它的庫與C、C++、D和Java的特性對比為基礎,主要在以下幾個領域:

    ·類機制(封裝與模板)

    ·異常

    ·內存— 重點

    ·大型文件的隨機訪問

    ·資源獲取初始化

    正如我在第一部分提到的,這里所做的測試只是針對程序性能的一小部分,硬件及操作系統的配置是測試語言 /編譯器/庫函數的機器的典型配置:2-GHZ,512-MB,Pentium IV,Windows XP Professional。 這里我們看到的任何結果揭示了在其它環境下可能的性能,這不說明可以假設這些結果就是正確的典型例子。 最后要說明的是,當我提到“C#比Java好”等結論的時候,一定是指“在特定的測試環境的條件下”,這一點請你銘記在心。

    性能評價

    所有的代碼還是用和第一部分一樣的編譯器編譯。 C#代碼在.NET框架 1.0.3705(可從http://microsoft.com/netframework/免費下載)下,由Visual C#.NET 編譯器v7.00.9466編譯。D代碼由Digital Mars D 編譯器 Alpha v0.62 (可從http://www.digitalmars.com/免費下載) 編譯。Java代碼由J2DKSE1.4.1-02 ( 可免費下載 http://java.sun.com/ ) 編譯。 C和C++代碼 ( 單線程 , 包括 raii , 和靜態鏈接庫 ) 由使用 STLPort v4.5的Digital Mars C/C++ v8.33 ( 可從 http://www.digitalmars.com/免費下載 ) 和使用 Visual C++ 6.0的頭文件和庫文件的Intel C/C++ v7.0來編譯。所有這些編譯器均使用最優化處理 ( set for speed ) ; Intel 用-QaxW flag [2]提供兩套編碼路徑:一個是針對有SSE2的Pentium IV處理器,另一個是針對其他所有的處理器——在運行時選擇合適的一個。(因為Intel編譯器只適用于Intel處理器構造,特別是在這個測試中的Pentium IV,這編譯器的編譯結果必須在此基礎上評價,不能在其他的處理器上進行同樣的過程,比如Athlon,甚至是舊版本的Intel處理器也不行。)

    后面將要描述我們測試的假想的五種例子中的性能表現。他們都用2-GHZ,512-MB,Pentium IV,Windows XP Professional的配置,并沿用在第一部分用的性能分析系統。每一種語言所編程序的調試都執行7次,同時沒有其他占線過程,去掉最高速的和最低速的,紀錄平均值。所有的時間信息,通過僅區分相應時間區間的高性能計時器保存下來。(C用戶調用Win32 API QueryPerformanceCounter() ;C++ 用 Win-STL ' s performance_counter [4], 可在http://winstl.org/找到 ; C# 用 SynSoft.Performance.PerformanceCounter ,可在 http://synsoft.org/dotnet.html找到; D 用 synsoft.win32.perf.PerformanceCounter , 可在http://synsoft.org/d.html找到;Java 用 System.currentTimeMillis() .)每一個測試程序都包含一個啟動迭代的程序,這樣可以將啟動或緩沖時的影響最小化。

    在每一個例子中的C#,C++和D的特征: C在所有 保存 封裝和 raii( 類)的地方出現,因為它沒有這兩種例子必需的語言特征。 raii 沒有出現Java,因為Java甚至連 Resource AcquisitionIs Initialization (RAII [1])的機制也沒有,所以它僅僅作為在GC-kicking的一個練習。簡單的說,我不會展示所有的語言的每一個例子的所有特性,但是所有這些都包含于在線記錄中(連同產生他們的Digital Mars make-compatible make-file)。

    下面就對測試性能的例子進行評價 :


    封裝 。這個例子( Listing1)的目的是比較不同語言(除了C)在類機制上的代價,這些語言并不完全支持相對于它們所做的所有類型的一般處理。尤其是C#和Java一般不能存儲和使用它們的內置類型實例,比如int,long,bool等等,同樣,對于從這些實例派生的類型也不行。相反C++和D都支持模板,它可以給任何類型的一般性操作帶來方便。在以后的文章里,我將詳細地講解容器。但在這個章節將不會用到它,如果提及它反而會分散對于封裝意義的理解。C#和Java中未封裝的變量傳遞ints給異或函數,封裝變量傳遞整形值在Int32(C#)和Interger(Java)的實例中。注意Java不支持模糊的封裝(這被證明是好的因為編譯器將對于這種封裝帶來的低效給予警告),所以實例必須明確地被創建(在列表1中標記“Java ” 處可以看到)。 C++和D的運行是簡單的使用模板,這就是它們的泛型機制。(在這里我不對“一切都是對象”的模板的好處作深入探討,但是我將在這期的姊妹篇Stepanov camp中對它做詳細講解)。

    異常處理 。異常的語義在各種語言之間有很大的區別。 在 C++,異常處理和調用析構函數一起沿著堆棧操作下來,然而在有垃圾回收的語言中,這些對象被丟棄,直到將來的某一時刻被收集。 這樣,就無法對各種語言進行有意義的比較。所以在這個例子中,只能通過將一段 using/aware代碼與另一段沒有異常處理支持的相同代碼進行比較。這個例子有三種測試,它們的目的就是去衡量使用和拋出異常處理的代價。第一(Listing 2)是比較使用異常處理和通過函數返回值來傳遞信息給調用者這兩者的代價。第二(Listing 3)是評估在try-catch模塊中執行的代價(盡管沒有異常處理被拋出)。最后(Listing 4)就是評估遍歷執行try-catch模塊的代價(同樣,這沒有異常處理被拋出)。




    Mstress。 這一個例子的目的是,在假設分配比回收優越的基礎上,看看垃圾回收語言是否在高的內存利用率上有重要的非線形關系。這里存在四種測試,每一個都牽涉到分配與釋放( C 和 C++)或者丟棄(C#, D, 和 Java)在一個時間段內的內存塊。這些塊大小是通過相同的偽隨機數算法生成的(詳見文章的第一部分),這能使塊的大小處于1至1000之間,從而在確保每種語言在相同的環境下測試時仿效現實世界中的分配。第一種測試(Listing 5)通過一個不斷循環的變量分配了1000個內存塊,目的是看看頻繁地分配會不會“留下”更多的碎片,這些碎片會阻礙分配的進行直到突然產生明顯的GC收集。第二種測試(Listing 5)增加了1000次循環中分配的次數,以測試由不斷增加的內存負擔引起的性能的 損失 。 第三和第四種測試(沒列出來)跟前兩個相似,它們增加了另外的內存塊和分配/回收的交叉活動,目的是為了模擬不斷增加的內存碎片。 C使用malloc()/free(),而C++,C#,D和JAVA用new[]。


    rafile 。這一個例子( Listing 6)評估各種語言從大文件中隨機讀取的能力。它利用某種偽隨機算法(詳見文章第一部分)來模擬在大文件中的查找/讀取。這里用到了兩個文件,一個4.2MB,一個21.1MB。在每一個查找點,四個字節被讀取,并且和一個運行值進行異或運算(這樣有利于證明所有的語言以相同的方式查找和讀取)。有意思的是,原始的運行程序讀了32位的整數,但不幸的是,JAVA用了network byte-order而不是host byte-order,這與C, C++, 和D的machine-order reading是不相容的。


    Raii(類) : 這最后的例子目的是為了評估沒有內建支持這個非常重要的語言特征(很不幸 C#和Java都沒有)而付出的代價。此例子表達了通過資源句柄XUser 類型從XFactory資源管理器分配X資源到客戶端代碼(見代碼段7)。在C++和D語言中,XUser 提供一個析構函數,在析構函數里分配的資源自動地回收到資源庫,資源在這種情況下是個信號量,.NET和D的執行代碼可從 http://synsoft.org/dotnet.html http://synsoft.org/d.html 下載。C#、D和Java 提供它們自己抽象的同步機制,但我需要一個信號量去引起交叉線程的競爭,去保證同樣的同步機制以便所有語言的進行生動的比較。此信號量在每種語言執行代碼中都有最初可獲得的五個鍵值。


    盡管 C#的XUser 類有一個析構函數,但此析構函數僅在實例進行垃圾碎片收集時被調用。(C#使用析構函數的語法,但編譯器把它翻譯成 CLR (公共語言運行時刻)的 Finalize() 方法。)這種情況跟 Java一樣。C# 還盡量更多地支持習慣用語(雖然它支持的還不夠),這是通過使用引用聲明來實現的,正如表7所示。(為了方便,“using-ed”類型必須實現 IDisposable 接口,否則聲明無效),一個明顯的問題是類的使用者必須承擔確保語義的正確的負擔,而不是類的開發者。這是奇怪的二分法:C#是面向對象的,但它依靠類的使用者對類的內部執行代碼的足夠理解以決定是否需要此類的直接析構。C++和D語言過程明確,由類自己決定:C++通過析構函數的執行,D提供析構或終止函數,類聲明驗證關鍵字 AUTO (沒有這些,它按C#和Java的方式運行正常的垃圾碎片收集程序)。

    C#語言使用 USING 聲明(和 IDisposable 接口)的一個可替代方法是依靠垃圾回收。在形成這個情況之前,我假設當內存使用率達到觸發閥值或者主線程空閑/等待的時候啟動垃圾碎片回收。(我不知道這種誤解是怎么形成的,或許只是異想天開)。當我在執行 Raii2 例子的最初版本的時候(變量沒有使用 USING 聲明),處理進程馬上掛起。很明顯,在其他線程堵塞時垃圾碎片收集沒有觸動,只有在內存消耗到一定程度才觸動。因此,為了程序執行,我們被迫在另一個工作線程中自己剔除垃圾碎片收集。當然,這在實際編程中是非常可笑的執行策略,執行結果證明了這一點。

    結論

    大部分的結果是以絕對運行時間數(全部循環或每循環)或者 C#的執行時間和C,C++,D,Java分別的百分比來表達的。在后一種方式,圍繞著百分刻度的結果表示C#的性能比率,高的值表示C#的性能優越,低的值表示相對低下。 Raii 的情況是個例外,在這里結果是以C++(Digital Mars)時間的百分比表示的,由于垃圾碎片收集代碼的消耗,調用時間值是以對數刻度表示的。這說明在對數比例上歸根于非常高的垃圾處理花費.


    BOX . 從圖 1中我們能看出,在合理范圍里(C#語言的97%-271%)對于所有編譯語言解除封裝操作(就簡單整形數據)成本的差別:最差的是C++(Digital Mars),成本是C#的271%,最好的是C++(Intel) 成本是C#的97%.假設執行相對比較簡單的 異或 運算, C#幾乎可以像 Intel那樣出色,你對這個并不會感到很驚奇,但你相信C#要比C++(Digital Mars)和D模板實例快兩倍.我有些驚訝C++(Digital Mars)這個相對弱的性能,特別是和D比起來它要慢得多,然而他們出自同一開發商。

    顯然這個封裝花費是有意義的 ,相當于C++模板花費4-10倍.這個測試僅僅包括整數,相對來說可能不能完全反映特殊類型.雖然如此,我們能有把握斷言這個模板輕易贏得了性能戰.有趣的是就他們各自的成熟度而言,C#要比java稍好,

    差距并不大 (小于4%).(我想可能由于.NET處理目標JIT3。【3】)。更有趣的是,事實上在未封裝的形態下,c#明顯比java快.


    除了圖 1以外,有人認為,不拋出的異常variants都是是比拋出的異常副本要快的(如圖2). 用 c和c++,Intel完成任務要比所有其它不拋出的異常variants快.c#緊隨其后。似乎c,c++,D(不考慮編譯器)在異常處理上花費大致相等,符合win32異常處理機制的預期限制因素.我們能看到三種語言在拋出異常variants上的差別是比較小的,很可能的原因是由于有相關的額外的管理消耗關系 (c++規定執行破壞功能的堆棧是從捕獲點到拋出點的來回).

    我發現有趣的是在拋出異常variants方面c#和java的執行是有關系的.

    因為這兩種語言語義在異常拋出處理要勝于返回當前錯誤值 ,也勝于其它語言,

    尤其 c語言所呈現的關系執行調用.我非常贊同Brian Kernighan和Rob Pike

    (<>,Addison Wesley,1999)所說的例外僅僅在例外條件的使用,并不是必然會發生的.然而人們能容易看到為何服務器在處理巨大數量的數據的時候可能出現的例外,而經常擔心性能的問題.正是如此,java以小于其它語言25%的消耗給人深刻的印象.由此我們限定win32,采用兩個有代表性的差不多的c編譯器來構造例外處理,雖然沒有定論,但是我認為java使用了不同的機制.

    那么c#的異常拋出性能如何呢?在相關條件下,相對其他語言而言,使用異常處理語句是不使用異常處理語句的21-77倍,而c#則是191倍;鑒于Java的出色表現,.net工具應該引起注意了。

    Except-2. 這個圖表是為了弄清楚在一個 try-catch范圍內異常處理語句的執行是否耗時,如圖3,大多數語言的表現同預期的一樣:c、c++和D對于Digital Mars是類似的;Intel c 和c++比其它都好.c#要比java快.相比較就性能而言,

    c#是很優勢的,令人感興趣的是,c#在進行異常處理是耗時比不進行時還要少.產生這種結果肯定是有原因的(這個結果是經過了多次驗證的), 但由于對于c#異常處理機制我沒有很深的認識,我不能做出合理的解釋.同樣,雖然并沒有定論,對于D來說,是否進行異常處理對結果似乎并無影響!.
    我認為這是因為它不用清除棧內數據, java也有大致相同的自由度,并且表現出的差異也并不大。


    except-3 如圖表 4所示,測試結果與預期大體相符。除了D編譯器的結果中微不足道的差異,所有的語言在遍歷異常處理語句的時候都比不進行是耗時要長,盡管如此,我仍然對在這兩種情況下差異如此之小感到吃驚。我們通常認為使用異常處理機制會耗費時間,并且我認為遍歷異常處理語句會在啟動和完成上耗費大量時間;事實上,我一開始就預期兩者的差異將會非常地顯著。然而,在這個測試當中,兩者的差異相當不明顯。

    但是這個結果并不難理解,導致差異如此小的原因首先在于:這次測試太過于純粹。這次測試的所有異常處理(類型)都是 int型的。盡管并沒有異常被拋出,這樣做的目的在于避免在異常處理體的構造(和析構)上耗費時間和消除各編譯器處理機制不同所造成的影響。其次,本次測試中沒有基于框架的結構體,因為在C++中,盡管析構函數并不會真正被調用,析構函數調用方面的準備也是必須進行的,這樣也會造成時間的耗費。(注意析構函數無論異常是否被拋出都會被調用。)這些耗費都會造成測試的不公平,所以我們選擇了int型。注意到程序不論是因為異常拋出而中止還是正常退出,析構函數都是要被調用的。所以它僅僅做為勾子而被添加進這些額外的函數調用中。然而,我認為所有的這些因素并不是很充足,它們僅僅使我們可以從那張表里知道當不使用的時候異常的開銷是非常小的。很自然,這也正確的表明了一個人對所有語言性能(perform)的期望,當然我們要給它足夠的信任.

    metress-1 這個測試很清楚的表明了在不使用內存分配機制的展示 (exhibit)中不斷增長的對固定數量的內存快(隨機大小)分配/釋放的循環的反應是非線性的。事實上,在很大程度上它們從沒有線性(增長)的趨勢:從性能上來說并沒有什么變化(見圖5),除了在一些低循環(low interations)上有所不同之外.(有趣的是,那些低循環的非線性是有意義的--占到了全部命令的50%還多--當然這僅僅是對c#和java而言).不管內存分配機制是否在每次特循環結束后都立即恢復對1000個內存塊的釋放,或者僅是簡單的把它們交給GC在以后的某個時間處理,語言/運行庫總是看上去幾乎完全不受這些循環執行的影響.

    在這組性能相對的測試中,我們可以清楚的看到一些它們之間的不同。在使用 Digital Mars 分配方式的語言中,C和C++的表現是最好的。Visual C++的運行庫比使用Digital Mars的語言低大概2.5-3倍,對于使用Digital Mars 的語言和Visaul C++運行庫來說,C和C++基本上是相同的。最后,很明顯,Java 比C#慢了3-4倍,而比C慢了差不多7倍.


    mestress -2

    就像我們從圖表中看到的,在一定數目的分配釋放內存的循環中內存塊(隨機大小)的不斷增長的反應是變量的引用是非線性表現的。這正是我們所希望的,因為在每次循環中分配的內存總量是按指數方式增長.每次循環所分配的內存的大小都低于10,000塊.使用Digital Mars分配方式的C 和C++的效率依然是極為優秀的。只是效率表現上低于平均值,而這里也不得不提到java,它的表現同樣不好.Visual C++的運行庫(C和C++都適用)相對于C#,D 和Java來說,有一個不錯的開始,但它很快就降到了一個很低的水平上。

    Java在每次循環是使用10,000內存塊之前的表現非常有競爭力,并在這一點急劇上升。D在整個測試中都幾乎有著一致表現.幾乎一直都是非線性的,這與C#很接近。

    如果可能的話,我都希望看到一條總夠長的X軸,C#在內存溢出的情況下仍然保持每次循環不超出10,000內存塊的水平并預防這種情況的出現。(D和Java似乎也做的到,但那也只是類似C#的行為一旦被發現就中止測試。)

    mstress-3 和 4 從前兩個測試(variants)看,除了時間的的增長,反復的交叉存取和塊變量在執行上的表現并沒有什么不同。曲線的走勢或者說不同的語言/運行庫的相對表現在這一點上沒有什么明顯的改變。

    我認為C和C++,在使用外部free/delete的條件下,重新使用的新近釋放的碎片。相反的,我很難想像出C#,D和Java 是如何使用垃圾收集機制周期性尋找每次循環所分配的內存從而盡可能的減少由內存碎片所引起的負面效應的,或者在這個跟本就沒有產生碎片.除去這些不同,這兩種方式的表現還是很相似的。

    這只是一種理想的我們所希望的分配機制的表現 ---畢竟,那是一個很極端情況,所有內存分配都可以在給定的周期內完全返回---雖然程序的執行都達到的目的。

    rafile. 這次測試中我所希望是那些語言實際 (效率)上并沒有什么不同.除了C++的執行也許比其它的低上幾個百分點.


    圖表 7中可以看出,C#的在文件的隨機存取上比C(使用Digital Mars)要好,但低于C++(使用Intel和VC6的連接庫),和D與Java現表現基本持平。但是C++運行庫表現出令印象深刻的性能。

    從所有這一系列測試來看,Intel 似乎已經能夠產生性能不錯的代碼了。但是它的連接的運行庫頭文件卻是Visual C++ 6.0的,這個(大概)不是由Intel編譯器產生的。因比它幾乎是以壓倒性的性能優勢超過了DMC,這主要是由于各自的Digital Mars 和微軟Visual C++運行庫的性能。(我承認有點吃驚,對于些測試結果正好可以反對兩個賣家的名聲--應該或是不應該得到的)。這也表明一個人的偏見是非常沒有理由的。

    另一件值的注意的事就是對不同大小文件訪問的開銷都非常的小。這也表明所有的語言都利用操作系統的優勢很輕易的達到了相同的程度。


    raII . 從圖 8中我們能看出使用statement的C#的表現只有C++析構函數效果的一半。D的性能與之相當,雖然產生的代碼中有錯誤(或者是D的Phobos運行庫),當進程創建的對像超過32,000左右的時候會使該進程掛起,從而防止過多(一個以上的)通訊中的數據點被確定的使用.在這組單獨的測試中我們可以看到RAII對C#和D的支持是很完善的,但并不如想象中那樣優秀。如有你要做很多scoping--并且你想,也許要足夠的robustaness的幫助---你最好還是選擇(stay with)C++。當依賴于.NET的垃圾回收機制時,因為處理過程只是簡單地被掛起,所以C#的性能比較難以測算。可是我們不希望.NET經常調用它的垃圾回收線程,但我找不到理由解釋為什么回收線程不以較低優先級在內核中等待,當有垃圾對象需要被回收和主線程空閑時再進行有效的回收。當然,我在垃圾回收方面不是專家,并且可能有一種合理的理論原因說明這為什么不是一個好的處理方法。

    GC(垃圾回收)即使響應的更靈敏,我們可以在它的性能結果上看出效果并不與之匹配。對應第二組數據指針的方法表明了GC每隔1ms被觸發一次。

    GC(垃圾回收)的方法比使用使用聲明慢了近千倍,所以這不僅僅是一個技術問題的范圍。自然,這跟關于這個例子的假設有關,同時跟幾乎每個人都用垃圾回收機制去管理頻繁爭用的資源的釋放這個不辯的事實有關,但我沒預計到這個變量的性能是如此之差。同時這也給了讀過Java/.NET書籍的C++程序員對于書籍建議(或許說依靠)使用終止函數來清除資源而感覺疑惑的一個解釋。

    結論

    在第一篇文章,我對于不同的環節得出不同的結論,這些環節或是語言本身造成的,或是庫或者二者造成的,在這里我也這么區分,因為語言特征而產生的影響的部分通常涉及:異常,封裝 /模板和RAII的實現。文件存取部分可以看作直接對庫函數的操作。(沒有任何一種語言阻止寫替換操作,雖然這樣做并不是很容易)。內存管理部分受語言和內存的影響――雖然我不清楚可以用哪一種管理機制去替代C#和JAVA的缺省的內存管理機制,但是對于其他語言這是很簡單的。

    封裝。當我們比較封裝和模板時,我們可以看出模板明顯比封裝出色很多。我相信這不會讓大家感到很驚奇,但是事實上封裝消耗的系統資源是模板的十倍。自然,作為模板類庫的作者( http://stlsoft.org/ ),我得出這個結論也許帶有偏見,但是這些數據在表示更廣泛的情況時,他們自身就說明這一點。在后面,我將說到容器和算法,我們將看到比預計更多的這樣的結果。

    至于有關異常情況,我想講四點:

    1.在所有的程序語言中,使用異常處理從調用的函數過程中返回,而不是保留他們作為異常事件的指示,將導致執行花費巨大的成本。

    2.C#的異常處理機制相對于其他被測試的語言效率是極低的.

    3.在有異常處理的上下文環境下運行(比如用try-catch的情況)對于性能沒有多大影響,除了C#,它為了提高性能(實際上,我對于這種結果很不理解,并且懷疑這是.NET運行期的一個人為結果而不是C#/.NET的一般特征。)

    4.交叉于異常上下文的執行(比如說進入try-catch和/或 離開try-catch)對于系統性能的影響基本上是非常小的。

    Raii. C#支持的RAII例子比C++在性能方面差,雖然不是很多,但與D相比差別無幾,基本一致。(這種一致指出了在處理堆分配對象的確定性析構時的基本限制)然而,從理論觀點,或從易用性和/或穩健性的實踐觀點來看,這里還是有很大差距的。C語言缺乏機制可以解釋為年代已久,并且它是一種程序語言。很遺憾,java缺乏這種機制,但是它只是可以解釋為忽視了。(至今為止我們已經用java8年左右了,所以“忽視”可能也有些牽強。)C#在這方面的努力還不成熟(因為它更多地依賴類的用戶而不是類的作者),很奇怪的是C#/.NET很多優點都是在Java中被看作瑕疵/遺漏的地方,比如屬性,輸出參數,和無符型。

    Mstress. 這個內存測試的目的是證明如果頻繁的內存分配加上垃圾回收機制是否會導致—— C#, D, 和Java這些包含垃圾回收的語言嚴重的非線形,這明顯沒有。這個結果可以看出所有的語言/庫表現的非常合理的。這里我們可得出幾個有趣的結論:

    1.C語言和C++語言,提供正確的庫支持,他們內存分配空間最快。毫無疑問,部分原因由于它們沒有初始化字符數組的內容。根據結果,我重新對C語言進行測試,用calloc()替代malloc(),測試結果很接近C#,雖然仍然會高出5個百分點。

    2.內存碎片(只要輪流存取第三和第四個變量)不會在很大程度上影響內存分配的性能,不會增加總體負擔。

    3.如果垃圾回收器安排在沒有使用的內存區之后不執行(我假設這是可以的),這將不會對性能有太大的影響。我假設,這說明了C#是第一個會內存枯竭的語言,所以我們可以假定它們通過使用大量的內存空間在內存分配性能方便取得平衡,并且相信在現實的環境中有這種機會運行垃圾回收。

    4.通常更希望體面地降低性能―就象C(Digital Mars)的方式――而不是在各個方面都有很強大的性能,然后在某些未知的閥值化為烏有:在服務器環境下,某一時刻提供一個慢點的服務比導致崩潰可能要好。由于對Digital Mars和Visual C++,C和C++的運行實際上是相同的,我們可以假定因為與通過new操作符的調用的交流而增加的成本可以忽略,并且在C和C++之間沒有本質的區別。

    5.C#內存分配時間會比Java快上3~4倍。

    總的來說,這個結果并不象我想象的那樣:我期望C和C++在中小應用比 C#,D,和Java稍微落后,但在大型應用中遠遠優于。真是學無止境。

    Rafile . 對于文件的隨機存儲結果可以看出一些重點。我認為最重要的一點是仔細的選擇庫。之所以C++的運行效果看上去要比C和其他的語言好很多,就是因為庫的原因。當把C++的性能(Intel 和VC6庫)和其他語言/編譯器進行比較時,用其他任何一種都很給人留下深刻的印象。自然,這邊的例子是故意的,但這么大的性能差別的事實——比C#和 Java快23倍,我們可以期待在現實情況中性能的有意義的差別。(這里,再次證明了偏見是錯誤的:我很厭惡所有的io流,從很多方面很容易能看出它的效率很低,當然也不能說全部這樣。我對于這樣的性能印象很深刻)。

    詳細的總結了C#的性能以后,我想就我們在其他語言方面的研究發現做一個簡短的介紹。

    1.C處理異常事件是不錯的,特別是在存儲和文件(提供正確的庫連接)有非常好的表現;它能提供所有我們能預期的效果,但是與后來的高級語言相比,吸引不了更多的學習者。

    2.C++處理異常事件也不錯,特別是在存儲和文件(提供正確的庫連接)有非常好的表現,而且包含模塊的概念和有效的RAII;它始終得到我的鐘愛,我認為它在未來的很長一段時間內都是編程的最佳選擇。

    3.D在異常事件處理上不是很太好,在文件處理上處于一般水平,在存儲方面有比較合理的相關性能和非常不錯的線性度,有模塊和RAII(雖然語言本身有很多的bug產生,使得很多的處理過程被掛起);當這語言經過它的初級階段,期望會得到不錯的效果。

    4.Java在封裝和內存管理的性能上表現最差,不過它有好的異常事件處理,但沒有一點模塊和RAII的概念;它不指望會達到真正的美好,我認為C#/.NET可以衡量出它的高低,至少運行在Windows平臺上 。

    摘要

    從第一部分開始,就給出全文的延伸的意思,從性能方面,如何能更好的用C#/.NET寫出很好的軟件。在第一部分里,一些結果雖然不是很值得注意,但是有很多的地方還是令人叫奇的(至少我是這樣的!)。

    這些研究結果展示了C#可以提供非常好的性能效果——至少在我們測試的范圍內是這樣——同時不能把它如同象過去把Visual Basic和一些擴展版本的Java當作一種性能差的語言來對待。對我而言,我比較注意語言運行的性能,當然,他們的表現也會超出我的預期效果。我所提出的嚴肅評論是從語言特征的觀點和對多個編程范例的支持方面得出的。

    作為一個最初使用C++的程序員,我從特定的觀點得出這些比較結果。這可以從RAII和封裝測試以及它們的語言解釋上看的出來。在某種意義上來說,這就好比比較蘋果和橙子,對于不同背景的人們可能會對我為什么如此強調模板和確定性析構感到疑惑,沒有這些他們也進行的很好。

    毫無疑問,模板給C++帶來了非凡的革命,它能支持群組合作,或者獨立運行,范例是無可比擬的。Java由于缺少模板和強制要求任何東西都是對象,而被批評了很長時間。.NET框架在這方面做法一樣也很讓人失望;可能由于Java缺少模板而他們可以在.NET環境下得到(他們確實達到)讓更廣的開發團體感到信賴并接受了它。

    缺少對RAII的支持對GUIs(通用用戶接口)是非常好的,這是在嚴格的框架內操作的軟件——即使象J2EE這樣精密復雜和高吞吐量的語言,和非關鍵的程序。但復雜的軟件不必要因為它而復雜。在最好的情況下,到處都是最終模塊,只要在Finalize()里函數里添加Close()方法就可以了。在最壞的情況下,滯緩或者隨機的資源泄漏都會導致系統的崩潰。更甚于以上的,如果OO(面向對象)——就象C#和Java所有都是對象——是你的目的那更讓我不安,第一個要失去的就是對象自己清除自己的責任,我沒辦法理解這個。(我知道一個非常出名的C++程序員——當然我不能告訴是誰,他告訴我當他在課程中突出RAII和模板的重點時,那些非使用C++的人們露出的滑稽表情,讓他感覺好象他丟失了什么東西)

    D是使用垃圾回收的語言,默認是非確定性析構,有很多地方與C#和Java相似,但是盡管如此,它還是兼容支持RAII的習慣同時具有模板。并且它是一個人寫的!我不明白為什么我們對Java或者C#(.NET其它語言也是一樣)印象如此深刻,即使有它們支持RAII和模板的缺點,而我們又能比較這些。有可能C#/.NET成功的原因和Java一樣,有大量的,有用的庫文件(C和C++應該從中學習,D應該去發展),和有一個強有力的后臺。

    最后,我想說對于所有的性能結果的比較分析,你必須明智地使用這些結果。我努力使自己公平,選擇我相信是公平和有意義的測試。但你一定要判斷的出這些例子僅代表了廣大可能性的的一小部分(不是無限意義上的),它們不是實際的程序,僅僅是測試而已并且把它簡化了,其中不可避免的帶有我個人的某種偏見在里面。通過逐步的方法,我希望降低這些因素。但你在閱讀和使用這些結果的時候要保持留意。

    感謝

    我要感謝 Walter Bright提供給我最新的Dv0.62版本,能夠完全的測試異常事件。感謝Intel的David Hackson給我提供了C/C++編譯器。還要感謝Scott Patterson幫我選擇了切合實際的測試方法(他總是不斷的在我煩躁、偏題、愚蠢的時候提醒我)。還要感謝Eugene Gershnik 對于我的一些斷言給了我嚴厲的反駁,幫助我適當地注意一些防止誤解的說明。

    Notes and References

    [1] The C++ Programming Language, Bjarne Stroustrup, Addison Wesley, 1997

    [2] The Software Optimization Cookbook, Richard Gerber, Intel Press, 2002

    [3] Applied Microsoft .NET Framework Programming 1 st Edition, Jeffrey Richter, Microsoft Press, 2002

    [4] Described in “ Win32 Performance Measurement Options, ” Matthew Wilson, Windows Developer Network, Volume 2 Number 5, May 2003.

    ]]>
    posted on 2005-09-15 10:24 Sung 閱讀(3595) 評論(2)  編輯  收藏 所屬分類: software Development

    FeedBack:
    # Seo-Kopiraiting
    2009-05-18 12:30 | Seo-Kopiraiting
    Good evening. By all means marry; if you get a good wife, you'll be happy. If you get a bad one, you'll become a philosopher.
    I am from Britain and bad know English, please tell me right I wrote the following sentence: "Seo ppc sem training and consulting agency in melbourne australia with global clients.Seo india - we are india based seo firm offers seo, ppc, seo consulting, social seo sem india has optimized some of the most successful, dynamic, database."

    With best wishes ;-), Mirra.  回復  更多評論
      
    # re: C# 與 C 、 C++ 、 D 、 Java 的性能比較 [第二部分]
    2011-02-28 15:41 | 糖糖
    第一部分呢  回復  更多評論
      
    主站蜘蛛池模板: 30岁的女人韩剧免费观看| a级毛片免费全部播放无码| 午夜国产精品免费观看| 亚洲最大免费视频网| 亚洲毛片在线免费观看| 亚洲国产精品综合久久2007| 99re在线这里只有精品免费 | 久久久久国产成人精品亚洲午夜| 99亚洲男女激情在线观看| 在线观看永久免费视频网站| 国产成人亚洲毛片| 亚洲激情黄色小说| 羞羞漫画小舞被黄漫免费| 免费一级毛片一级毛片aa| 特色特黄a毛片高清免费观看| 亚洲人成人网站在线观看| 成全视频在线观看免费| 亚洲国产一区二区a毛片| 在线看片免费人成视久网| 国产精品亚洲四区在线观看| 拔擦拔擦8x华人免费久久| 一级毛片a免费播放王色| 亚洲人成77777在线播放网站| 7m凹凸精品分类大全免费| 久久亚洲精品专区蓝色区| 国产乱色精品成人免费视频| 亚洲国产美女精品久久| 在线不卡免费视频| 国产免费伦精品一区二区三区| 亚洲国产精品久久| 在线a毛片免费视频观看| xxxxxx日本处大片免费看| 97亚洲熟妇自偷自拍另类图片| 成人毛片免费观看视频在线| 亚洲H在线播放在线观看H| 免费精品无码AV片在线观看| 久久亚洲精品国产亚洲老地址| 亚洲国产精品狼友中文久久久 | 特黄aa级毛片免费视频播放| 国产亚洲福利精品一区| 永久黄网站色视频免费|