C# 性能能趕上編譯型的 C/C++/D 和中間代碼運行時解釋的 java 嗎?
微軟發布了 .net 平臺和 .net 平臺支持的多種語言,包括 C#,vb.net, 受管 (managed extensions) 的 C++. 它們構成的體系能同時滿足開發出高效的服務器架構,和開發部門對強壯的多功能的接口的需求.
和其他眾多程序員一樣,拿新開發語言與日常用到的語言作比較和選擇的時候,我對新玩意總有莫名的偏見和懷疑.本篇文章將會討論 .net 平臺的優點,特別是相關的 C#. 我們拿 C# 和其他開發語言在高性能軟件開發方面,作定量的性能比較.
因為 c# 程序編譯運行分為兩部分:先編譯成中間代碼,然后是中間代碼的運行時解釋執行,所以我們不僅會把他跟純編譯型的 C/C++/D 語言相比,也會把他與典型的 ( 中間代碼運行時解釋 ) 的 Java 語言作比較. Java 無疑是一個成熟的語言,應用在各個計算機領域. Java 程序先被編譯成中間代碼,然后運行時通過 java 虛擬機解釋成本地機器碼執行.由此看來, .net 平臺,特別是為首的 c# 語言,借鑒了 java 的很多東西,包括語法和運行時支持.
對新語言(或者說時語言 + 對應的庫 + 該語言的運行時支持)作完整公平的性能比較算是一個龐大考驗.本文對 c# 與 C/C++/D 就開發中都涉及到的基本共通點作初步的性能比較,同時還包括各個語言所帶的庫的性能比較.包括:
1. 運行一次完整的程序(轉載執行一個程序的時間)
2. 算法和遞歸(計算圓周率和 Eratorshnes's sieve )
3. 類型轉換( int->float,float->int,int->string.string->int )
4. 字符串比較
5. 字符串連接
6.( 字符串特定符號統計 )
通過上面的一系列比較,將會揭示 c# 編譯器效力的一些有意思的地方和 .net 運行庫的性能
背景 :
雖然作比較的語言都可以算是 C 語言系列,雖然這五種語言在語法上只有或多或少的不同,但在其他方面,它們的差別是巨大的.(見表一, http://www.digitalmars.com/d/comparison.html ) . 我假設讀者已經熟悉 C/C++/Java ,以及一些基本的 C# .對于 D 語言,不如其他語言那么有名,但你可以找到它的介紹
D 語言介紹
D 語言是 Walter Bright 正在開發的一種語言,安裝和學習這種語言的開銷很小。實質上, D 語言的目標是更成功的 C/C++ 語言,改進包括可變長度的數組,函數參數的 [in][out] 屬性,自動內聯,版本控制,局部函數,內建單元測試,嚴格的類型檢查等等,包括 Java/C# 中實現的忽略指針和引用的差別 , 這在 C++ 中只有通過模板實現。它同時支持對現有 C 函數和 API 的直接支持。
它支持許多跟 Java/C# 類似的特性 , 比如垃圾收集。 D 語言跟 C#/Java 的本質區別是 D 語言運行時不需要運行時的虛擬機支持。 D 語言所支持的所有特性都是在編譯連接時完成的,所以不需要運行時的額外支持。 D 語言的開發目前已經到了 Alpha 版本,將來的成熟版本在絕大多數情況下將會比 Java/C# 高效得多,除非 JIT 優化非常明顯.所以我們才把 D 語言加到我們的測試當中。實際上, Walter 告訴我們, D 完全有能力產生跟 C 代碼運行得一樣快的代碼,同時,由于自動內聯,內建數組,字符串內建優化,非 \0 的字符串結束標記等特性,他相信在 D 語言正式發布的時候,它的性能將會超過 C 。
在真正的測試開始以前,我假定性能按照從高到低排序,應該是 C/C++,D,C#,Java .我跟其隨后證實的其他好多人一樣,一開始就懷疑 C# 產生高性能代碼的能力.
性能比較
我們會在下面接著介紹的十一種不同情況下測試性能.測試平臺是 2G 512M 的 pentium4 ,操作系統是 Windows XP Pro. 測試是在一套我自己設計的工具下進行的,為了符合這篇文章,我理所當然地用 C# 寫了這個工具。在沒有其他干擾進程的情況下,對每個測試者運行七次,分別去掉最快的一次和最慢的一次后得到的品均值,就是測試者的成績。除了下面說到的 noop 測試,所有的測試通過高精確度定時器計算完成所有內循環所需時間完成。(譯者注:比如 int->float 測量中,會通過循環運行多個 int->float 的轉換,然后測量完成整個循環需要的時間)。如 Listing1 所示的代碼,就是用 c# 完成的 f2i 測試。 (C 通過調用 win32 api 函數 QueryPerformanceCounter() ; C++ 通過 WinSTL 的 performance_counter;C# 通過 SynSoft.Performance.PerformanceCounter;D 通過 syncsoft.win32.perf.PerfomanceCounte ; Java 通過 System.currentTimeMillis()) 。每個程序都包含進入實質測試代碼前一段 ” 熱身代碼 ” 來減少程序初始化 ,catch 等因素帶來的不利影響。
編譯器版本號如下:
C#:VC#.net v7.00.9466,.NET Framework v1.0.3705;
D: Digital Mars D compiler Alpha v0.61
Java: J2KDSE 1.4.1
C/C++ 在兩個編譯器下完成,分別是 Digital Mars C/C++ v8.33,STL Port v4.5 和 Inter C/C++ v7.0 ,頭文件和庫用 VC++ 6.0
用 Digital Mars 編譯器的原因是:首先它能快速生成高質量代碼,而且免費。其次是為了和 D 語言作比較,因為 D 語言用到相同的 C/C++ 連接器和很多 C 庫。
為了作徹底的性能比較,所有的編譯器都用了速度最優的優化方案。 Inter 編譯選項是 -QaxW ,它提供兩種代碼路徑,一種是專門為 Pentium4+SIMD 優化,一種是為其他 cpu 優化。具體的代碼運行路徑在運行時自動選擇。 ( 通過對不同 C/C++ 編譯器性能的分析,我認為 Inter 編譯器針對他自己芯片的優化非常好,而對于 Digital Mars 的編譯器編譯出來的代碼性能,可以代表普遍的 C/C++ 性能 )
對這幾種語言作方方面面的比較是不太現實的,因為某些特定的功能并沒有在這幾種語言中都得到實現。比如 C 內建庫里沒有(字符串特定符號統計)函數。不過既然這篇文章的主題是討論 C#, 所做的測試都是 C# 支持的,如果其他語言沒有提供對應的功能,我們會提供一個自定義的實現。同時,如果某種語言對某種功能的內建支持非常差的話,我會考慮提供一個相對好一點的自定義支持。 ( 特別是對于 C/C++ ,至少有一兩個地方看得出效率不高的內建庫掩蓋了語言本身的真實性能 )

簡而言之,我不會列出每種測試在不同語言的具體實現代碼,不過在這里,我會詳細介紹下面幾種測試情況 :
noop.noop 測試的是程序的裝載時間 : 一個空的 main/Main 的執行時間。跟其它測試不一樣,這個測試是在 ptime 工具下完成的 .( 可以從 http://synesis.com.au/r_systools.html) 獲得,它提供了了 unix 環境下 time 工具類似的功能,同時它還能多次運行目標程序來獲得平均值 ( 在我們的測試中, C/C++/D 運行 200 次 ,C# 運行 50 次, java 運行 30 次 ). 為了除去某些特定情況造成的影響,我們會多次測量 ( 譯者注:指多次運行 ptime 程序, ptime 程序自身又會多次運行目標程序 ) ,并且取去掉最高值和最低值后的平均值。
f2i 這個測試把 1 個長度為 10,000 的雙精度浮點值轉換為 32bit 的整數。從 Listing 1 可以看出,使用偽隨機數產生算法可以保證各語言比較相同的浮點數。這帶來了兩個有趣的情況,我們將在查看結果的時候予以討論。

I2f 跟前一個相反,這個測試對針對具體某個函數進行的測試。程序生成 10000 個確定的偽隨機整數,保證在不同語言中被轉換的整數是相同的。
I2str 把整型轉換成成字符串型。整型變量從 0 循環增加到 5000000 ,每次做一個整型到字符串型的轉換 ( 見 listing2).C#/Java 用內建函數完成 ---i.ToString() 和 Integer.Tostring(i),C/D 用傳統的 sprintf() 完成。 C++ 分為兩次完成。第一次用 iostream 的 strstream 類,和預期的一樣,這種情況下的性能非常差。為了提高效率,我采取了一些措施,所以導致了第二種方案,見 i2str2

Str2i. 把字符串轉換成整型。建立一個字符串數組,內容是 i2str 轉換過來的內容,然后再通過語言本身的支持或者庫函數把字符串轉換回去,計算轉換回去的時間(見 listing3 ) .C/D 用 atoi 完成, c++ 用 iostream 的 strstream 完成, c# 用 int32.parse 完成, java 用 integer.parseint() 完成。

picalc 迭代和浮點。通過 listing 4 所示的迭代算法計算圓周率。每種語言的實現其迭代次數是 10,000,000 。

Picalcr. 遞歸。通過 listing5 所示的算法計算圓周率。對每種語言,我們會做 10000 次運算,考慮到 java 堆棧在深層地歸時很容易被耗盡,所以每次運算限制在 4500 次遞歸以內。

Sieve. 疊代。通過 Listing 6 所示的迭代算法( Eratosthenes's sieve ( http://www.math.utah.edu/~alfeld/Eratorthenes.html ))得到質數。每種語言的實現其迭代次數是 10,000,000 。

Strcat. 字符串連接 --- 通常是大量運行時開銷的根源,也是導致程序低效的潛在領域。(我以前做過一個對性能敏感的項目,通過改善效率底下的字符串連接,我獲得了 10% 的整體性能提升) . 測試通過構造四種不同類型的變量 :short,int, double 和一個 string ,然后把它們連接成一個 string 變量完成,連接時添加用分號分割.在連接的時候采用四種不同的連接方
法 :1) 默認連接方法 2) 故意構造出來的導致效率低下的連接方法 3)C 語言種典型的 printf/Format 格式,需要重新分配空間 4) 為提高性能而進行的硬編碼 . 代碼摘錄見 Listing7

Strswtch. 通過級聯 switch 進行字符串比較 . 通過語言支持的 switch 語句進行比較或者通過一些列的 if-else 語句模擬( Listing 8 ).這項測試是為了檢驗 c# 的字符串處理功能是否如他所聲稱的那樣高效.測試分成兩部分 : 首先是進行字符串之間的賦值,也就是同一身份的比較(譯者注:也就是字符串內部的指針直接復制,不涉及到字符串內容的拷貝).其次是通過字符串的拷貝后進行比較,這樣就避免了同一身份比較而是進行進行字符串之間的字面比較.每種語言上都進行 850000 次疊代,因為超過這個次數后會導致 java vm 內存耗盡.
strtok 字符串分段。字符串的運行效率也極低,有時甚至引發嚴重的運行開銷。這個測試( Listing 9)讀取一個文本文件的全部內容至一個字符串,然后拆分成段。第一個變量用字符分隔符:‘;'分隔字符串,并忽略所有的空白。第二個變量也用分號分隔,但保留了空白。第三個和第四個變量用字符串分隔符:<->,并相應的忽略和保留了空白處。C里僅第二個變量用了strtok(),C++四個變量都用STLSoft的string_tokeniser模板,但各自正確給參數賦值。C#第一個變量用System.String.Split(),2至4變量用SynSoft.Text.Tokeniser.Tokenise();Java用java.util.StringTokenizer,僅支持變量2和4(注:我選用STLSoft的string_tokeniser模板不是我要為STLSoft作宣傳,而是測試表明它比Boost的快2.5倍,而Boost是大多數開發人員在查找庫時的首選)。

結果
在 noop測試中,表2列出了以微秒(us)為單位裝載一個空操作的平均開銷。顯然C,C++,D的差別不合邏輯,每一個大約是5-6毫秒,而Intel確低至2ms。我所想到的唯一可能是這個編譯器優化了所有的C實時運行庫的初始化工作。如果是這樣,這個性能在其他非試驗性即實際應用中不能體現出來,因為它至少需要一些C … ,這有待深入研究。

C#用了大約70ms,Java約0.2秒。C#和Java的裝載清晰的表明其實時開銷在于它們的實時運行的結構基礎(VM和支持DLLs)。但是除了大規模的命令行的商業化批處理,對極大型系統(上百萬條代碼)或CGI的基礎構建,啟動開銷在大多數情況下不重要。重荷服務器允許在數秒內啟動,所以語言間200ms的差異就微乎其微了。

其余的結果分三部分顯示。圖 2和3分別展示了字符串連接和分段的結果。圖1包含了其余全部的測試結果,我們現在先來看看它。


圖 1中,對各測試項,C,C++,D,Java的結果均以其相應測試項C#運行時間的百分比顯示。因此,在100%線上下波動的結果說明與C#性能相當。高百分比說明C#性能較優,而低百分比表明C#性能相對較差。
f2i 拋開Intel不談,可以看出C#在浮點到整型的轉換中效率遠遠高于其余四個,僅是C,C++, 和D開銷的2/3,是Java的2/5。在我讓它們使用相同的隨機數發生器前,不同語言的性能差異極大,這說明轉換速度非常依賴具體的浮點數值,當然這是后見之明了。為了確保每個語言做相同的轉換,程序計算轉換值的總和,這也有助于任何過度的優化去除整個循環??梢缘弥@種轉化在語言間正確度不同。每種語言的計算總和迭代10,000次的結果在表3中列處。它們表明(雖然無法證實)C,C++,和D是最正確的,Java其次,C#最差。C#犧牲正確性和降低處理器浮點精度(參見 引用)換取優越性能是令人信服的,但事實上做此判斷前我還需要更多的證據。至于Intel,說它的性能沒有什么更卓越的也是公平的。

5個語言這里效率相當。另4種性能比C#低5%以內。唯一叫人感興趣的是這四個語言都是性能比C#略好。這表明C#確實效率低,雖然在這方面只低一點。這與從整型到浮點型轉換的結果相反。同樣,值得我們注意的是,雖然Java在浮點到整型的轉換中性能很低,但在整型到浮點的轉換中卻是最好的,比C#高10%。對f2i,Intel表現極佳,其C和C++分別只用了C#時間的10%和23%。
i2str 整型到字符型的轉換。 C#比C和D的printf略好。比C++的以iostream為基礎的轉換效率要高。它是C#運行時間的4倍(對Intel 和VC6 STL/CRT是5倍)。但Java性能出色,用了C#2/5的時間。因為C++的效率低下,第二個變量使用STLSoft的 integer_to_string<> 模板函數。它在很多編譯器上比C和C++庫的所有可利用的轉換機制要快。在這種情況下,性能比C#高10%,比C和D高約20%。但是因為Intel編譯器的優化影響, integer_to_string<> 似乎找到了其完美搭檔:其時間比C#低30%,是第三個速度超過Java的,比Iostream快17倍以上。認為它很可能已經接近了轉換效率的上限也是合理的。
str2i 字符型到整型的轉化結果與前面大相徑庭。 C和D用atoi比C#快約5-8倍,比Java快3倍,Java比C#效率高得多。但是這四個比C++以iostream為基礎的轉換都快。C++用 Digital Mars/STLPor運行時間t 是 C#的2.5倍,用 Intel和VC6 CRT/STL 是 10倍。這是iostream的另一個致命弱點。但因此說C++效率低下未免有失公允(實際上,我正在為 STLSoft 庫寫字符至整型的轉換,以和它 integer_to_string<> 的杰出轉換性能相匹配。最初的結果表明它將優于 C的atoi,所以這個轉換中C++也是優于C的。我希望能在本文刊發前完成此轉換。請參閱 http://stlsoft.org/conversion_library.html 。 )
picalc 在這里各語言性能表現相近。 C#和Java幾乎相同,比C,C++效率高上10%。C#的性能可以這樣解釋:它的浮點數操作效率高,這可以從f2I看出。但這不能理解Java的表現。我們可以認為C#和Java都能較好的優化循環,它涉及函數調用,但這有賴于深入研究。既然性能相差10%以內,我們只能得出這樣的結論:各語言在循環結構性能方面差異不顯著。有趣的是,Intel的優化這里沒有實際效果,可能是因為Pi值計算太簡單了吧。
Picalcr : 這里的結果可以更加確定一點, C , C++ , C# 和 D 語言的性能偏差在 2% 之內,我們可以公平地說它們對于遞歸執行的性能是一樣的。 Java 卻是花費比其他四種語言多 20% 的時間,加上 JVM ( Java 虛擬機)在超過 4500 次遞歸時堆棧耗竭,可以明顯地從速度和內存消耗方面得出 Java 處理遞歸不是很好。 Intel 的優化能力在這里有明顯的作用(讓人印象深刻的 10% ),但我在這里并沒有詳細分析這一點。
Sieve: 相對簡單的計算質數的算法(僅包含迭代和數組訪問)說明 C , C++ , C# 和 D 語言在這方面事實上是一樣的(僅 0.5% 的偏差),但 Java 的性能低了 6% 。我認為這中間大有文章, C , C++ 代碼不進行數組邊界安全檢查,而 C# 和 Java 進行數組邊界檢查。我對這種結果的解釋是: C# 和 D 語言在 FOR 循環內能根據對數組邊界的條件測試對邊界檢查進行優化,而 Java 不行。由于 C# 是新興語言, D 語言還沒發行,這就讓人不那么失望了。即使這不是為 Java 辯解,也不會讓人印象深刻了。再說, Intel 優化起了明顯的作用——增長 5% 性能,正如 picalcr 例子,這相對于其他語言更讓人印象深刻。
Strswtch : 這個測試對于 C# 語言不是很好。即使是 C++ 低效的字符串類操作符 = = 都比 C# 的性能快 2.5 倍 (Digital Mars) 到 5 倍( Intel VC6 STL/CRT )。 .NET 環境下字符串是內部處理的,這表示字符串存儲在全局離散表中,副本可以從內存中消除,等值測試是以一致性檢查的方式進行的 ( 只要兩個參數都是在內部 ) 。這明顯表示內部修復機制的效率嚴重低下或者一致性檢查沒有建立 C# 的 ”string-swatching” 機制性。 C# 的這種嚴重低效率更讓人傾向于后者,因為想不到個好的理由解釋為什么會這樣。正如例 8 所示,變量 1 的字符串實例是文字的,都在內部(事實上,這里有個限制沒有在例 8 中表示出來,在變量 1 的預循環中用來驗證參數是真正在內部的)。
當在變量 2 中假裝進行一致性檢查,發現僅 Java 的性能有明顯的提高,從 2% 到令人印象深刻的 30% 。很明顯 Java 在使用一致性檢查所取的性能是不切實際的,因為在真正的程序中不能限制字符串都是文字的。但這可用來說明支持內部處理和提供可編程實現訪問這種機制的語言的一種性能。諷刺的是, C# 作為這五種語言之一,它的性能是最差的。
Strcat : 表 2 以微秒( μs )顯示了五種語言每一種在這個例子中的四個變量的花費時間。本文沒有足夠的篇幅來細節描述 17 執行代碼,但盡量保持例子 7 的四個變量的真實面貌。變量 1 和 2 在所有語言執行代碼中涉及以堆棧為基礎的字符串內存,同時 C , D 語言在第三個變量中、 C , C++ , D 語言在第四個變量中用結構內存,這在某種程度說明他們的性能優于其他變量和語言的執行代碼。本例子的 C , C++ 的討論適合于 Digital Mars 版本,因為很明顯的 Intel 所帶來的性能提高被 Visual C++ 運行時刻庫的效率(非期望)所抵消,事實上 Intel 的每種測試的性能都比 Digital Mars 的差。
第一種測試是以默認的方式執行的,很顯然 C# 和 D 語言的性能都好于其它語言。很奇怪的是 Java 的性能最差,因為 Java 被認為有能力在一條語句中把字符串序列解釋成字符串構件格式(在變量 4 中手工轉換)。
第二種測試,眾所周知它的格式不好,透露了很多東西。它的劣性根據語言而不同,對于 C# , D 和 Java ,包含在連續的表達式中連接每一項,而不是象第一個變量在一個表達式中。對于 C ,它簡單地省略了在開始多項連接重新分配之前的的內存請求。對于 C++ ,它使用 strstream 和插入操作符而不是使用 str::string 和 + ()操作符。結果顯示所有語言不同程度地降低性能。 C 內存分配器的附加輪詢看起來沒有起多大作用,可能因為沒有競爭線程造成內存碎片,所以消耗同樣的內存塊。 C++ 改變成 iostreams 使代碼更緊湊,但沒有轉變數字部分,它們僅被插入到流當中,所預計的執行結果驗證了這一點。 D 因為這個改變降低性能許多( 32% ), Java 更多( 60% )。很奇怪的是 C# 性能在這里僅降低很小,少于 7% 。這讓人印象非常深刻,意味著可能在 .NET 的開發環境下因為 Java 字符串連接而形成的代碼回查的警覺本能會衰退。
第三種測試使用 printf()/Format() 的變量不足為奇。通過使用部分 / 全部的幀內存, C , C++ 和 D 達到很好的性能。奇怪的是 C# 的性能僅是 22% ,簡直不值一提。 Java 根本沒有這個便利功能。(以個人的觀點也是不值一提的) C#和D語言都能夠在封閉的循環中,依照數組邊界測試情況,優化他們的越界檢查。
最大的 測試 -硬編碼的性能-這個是很有趣的問題,因為 C++還是能提供超級的性能,如果能夠依照問題的精確領域來編碼,即使別的語言(C#,java)使用默認的或者性能優化的組件或者理念也是如此。在C#和java上使用他們各自的StringBuilders能夠提供真實的效果,達到他們在這個集合中的最佳性能。然而,C#能發揮最佳效率的機制,還是不能與C/C++、D語言相比。更糟糕的是,java的最佳性能都比別的語言的最差性能要可憐,這是相當可悲的。
總之,我們說對底層直接操作的語言垂手可得地贏得那些轉化率工作得很好的或者完全地在中間代碼中,并且很顯然地能夠提供良好性能的語言用法對java是很重要的而對C#是不太重要的。請注意這一點。
Strtok . Table3顯示了五種語言的每一個環節總共的時間(單位是毫秒ms)。同時他們的空間占用在這篇文章中并沒有提及,但是所有語言的實現都是非常直接的并且很明顯很接近在Listing9中列出的四個問題的本質。與strcat一樣,基于同樣的原因,使用Intel編譯器的結果沒有討論。
第一個問題――分割字符,保留空格――顯示C++版本是明顯的勝利者,D語言要慢20%,C# 要3倍的時間。顯然,這個與庫的特征的關系比語言的更明顯,
并且我使用 tokenizer比著名的Boost更多一些,那樣的話,D語言要比C++快2倍,并且C#只慢20%。雖然如此,STLSoft的tokenizer是免費得到,我想我們應該把這個作為C++的一個勝利。(其實,template在這一點上的使用STL的basic_string作為他的值類型,這個并不因為他的良好性能而聞名,這可能引起爭論-我并沒有使用一個更有效率的string,就像STLSoft的frame_string.總之,我認為這是個公平比較)。
第二個問題--分割字符,去掉空格--包含所有語言的實現版本。自然地,C語言贏得了比賽,它使用了strtok()函數,這個函數在創建tokens時并不分配內存并且直接寫終結符NULL到tokenized string的結尾。盡管有這些不利條件,C++的性能還是很好的,時間是C的221%,比較好的是D語言,432%。
C#和java就很差了,分別是746%和957%。這簡直是不敢相信的,C#和java運行的時間是C++的3.4倍和4.3倍,這三種語言都為tokens分配了內存。我相信這個是STL模型在處理iterable sequences時比passing around arrays更有效率的很好的例子。(請注意,我們有高度的信心這是場公平的關于C++,C#和D語言的比較,我寫了三種tokerizer的實現,都是公開的,可得到的.)
第三個問題--分割句子,保留空格--顯示了兩件事情。第一,在這三種語言在實現這個程序的代價比實現單個字符更昂貴。第二,我們發現D語言取代了C++的最佳性能的地位,領先5%左右。C#繼續被甩下,大概是另外兩種語言的1.8倍時間左右。
第四個問題--分割句子,去掉空格--沒有任何令人意外的,C++和D擁有大概一致的性能。C#比較不錯(大概2倍的性能差距)java更慢(差不多3倍時間)。對于C++,C#和D來說去掉空格比不去掉空格導致一小點的性能損失。很明顯的,在C#中,這么做比其他兩個的損失更大一點。很有價值的是,這些功能在C#和D中是已經實現的。由于數組在C#中是不可變的,從tokens中返回一個沒有空格的數組將導致重新分配一個數組。可是,D允許指派數組的長度,這個能動態地調整數組的大小這是個很好的例子說明D語言提供更好的效率。
總的來說,考慮到性能和自由度,我們能夠說 C++是勝利者,D是很接近的第二名。C擁有最佳的性能,但是只支持一種類型的tokerization。C#表現得很差,java更甚。
總結
我們可以把結果分成3種情況:這些包括語言的特性,包括庫的特性,或者兩者都相關。這些只與語言相關的特性顯示在 f2i, i2f, picalc, picalcr, and sieve scenarios,這些在語言選擇上的作用是很小的。c??雌饋碓诳傮w上是最好的,但是有點讓人不能信服的是它在f2i中取得的優異性能并且是因為犧牲了浮點精確度而取得效率。(這一點需要更多大檢查,我將在下一篇文章中去做。)
java是明顯最差的 。
當討論intel編譯器的浮點計算的深度優化效果時,它很明顯地顯示,c和c++能夠做的比C#更好。我討論這些并不是說這些語言必須要更好的效率,而是說他們能比其他的語言提供更好的優化和增強的機會。這個大概是與本主題完全不相干的是,C和C++比別的語言有更多的編譯器;更多的競爭,更聰明的人擁有不同的策略和技術。編譯器的結果的運行目標是處理器,這將導致戲劇性的效果;我們僅僅在表面上探討了這個龐大的問題,但是我不久將在以后的文章中回到這個主題。
Strtok 是唯一一個能說是library-only的,在這一點上C#干的并不好。雖然比java快,但是比其他的語言慢2倍或更多。同樣令人失望的是VisualC/C++的運行庫。在這一點上包括語言和庫的效果的是i2str,str2i,strcat和strtswtch,描繪的并不是很清楚。C#明顯的在string的拼接這一環節上比java好得多,但是明顯地比其他的差。關于尊敬的C,C++,D,只是在一些環節上領先,在另外一些環節上相當地差。
很有趣的是定制的C++的庫替換加上深度優化的intel的編譯器。
總之,不可能做出定量的結論。從語言的表層來看, C# 和 Java 挺簡單的,但低效的庫降低了速度。我覺得,當相關的庫還不如語言本身的時候, C# 的確實有微弱的優勢;但反過來,當庫逐漸完善,超過語言本身后, C# 明顯就不行了。但還是能看到一點點希望: C# 還是有可能實現預期的目標,因為庫比語言更容易實現組裝。但是,人們認為由于跟別的語言相比, C# 和 Java 所用的庫與語言結合的更加緊密,這些庫就可以作為這兩種語言效率的關鍵所在,至少對當前的版本來說是這樣的。
正如我所作出的示范(從整數到字符)以及相關提到的(從字符到整數),有人可能會批評者并不符合這篇文章的原意。
更深入的探討
本文主要探討了一下 C# 對 C++ 和 Java 可能造成的“威脅”。總的來說,這結果雖然不怎么鼓舞人心,卻足以讓人吃驚。從效率上看, C# 跟 C 以及 C++ 這些過去的同類產品(假如它們是更高效的)相比只能算是一般水平,至少在基本語言特征的比較上是這樣的。從某種程度上來說, Java 也是如此。(我承認我無法證明 C 和 C++ 相對于 C# 的絕對優勢。在多年前我的畢業論文分析調查期間,我認識到令人乏味的結果正和令人興奮的結果一樣生動形象,但前者花費更小的經濟支出。)
上述結論對于多處理器以及(高性能的文件/網絡處理系統)等來說卻不一定適用。不過,這確實很好地展現出了這些語言的基本效率以及更復雜的運算所依賴的最基本的庫。
就 D 發展狀況來看,現在就對它進行這樣的歸類似乎為時過早,不過它現在的表現確實不錯。 D 的效率至少是 C# 的 167% ,其實大多數情況下還不止。有些人指出,目前僅僅還處于字符時代,只需幾個人就能完成一個編譯器及其附帶的庫。我猜測, D 有可能發展成一門強大的技術。
就我個人而言,作為一個對 C# 持有偏見的人,偶然還是會對它的性能感到驚訝。我熱切地期盼著能將性能的對比分析延伸到更多領域:復雜而高效的內存的應用,多線程,進程通信,特殊文件處理,手寫代碼開銷,這些技術都能讓我們對語言進行更深入的研究。能看到 Inter 公司怎樣通過定制的 C Library (比如 CRunTiny ;參考 http://cruntiny.org/ )和 STL (比如 STLPort )。
鳴謝
感謝 Walter Bright 為我提供了一份言簡意賅的關于 D 的介紹,并及時地指出了一些當時被我忽略的優點,使我能對測試工程進行改進。同時也要感謝 Sun Microsystems 公司的 Gary Pennington ,他為我提供了關于 Java 方面的資料。還有 Scott Patterson ,他對本文的草稿進行了大量的精簡,并對全篇文章進行了細致的檢查。
C# 性能能趕上編譯型的 C/C++/D 和中間代碼運行時解釋的 java 嗎?
微軟發布了 .net 平臺和 .net 平臺支持的多種語言,包括 C#,vb.net, 受管 (managed extensions) 的 C++. 它們構成的體系能同時滿足開發出高效的服務器架構,和開發部門對強壯的多功能的接口的需求.
和其他眾多程序員一樣,拿新開發語言與日常用到的語言作比較和選擇的時候,我對新玩意總有莫名的偏見和懷疑.本篇文章將會討論 .net 平臺的優點,特別是相關的 C#. 我們拿 C# 和其他開發語言在高性能軟件開發方面,作定量的性能比較.
因為 c# 程序編譯運行分為兩部分:先編譯成中間代碼,然后是中間代碼的運行時解釋執行,所以我們不僅會把他跟純編譯型的 C/C++/D 語言相比,也會把他與典型的 ( 中間代碼運行時解釋 ) 的 Java 語言作比較. Java 無疑是一個成熟的語言,應用在各個計算機領域. Java 程序先被編譯成中間代碼,然后運行時通過 java 虛擬機解釋成本地機器碼執行.由此看來, .net 平臺,特別是為首的 c# 語言,借鑒了 java 的很多東西,包括語法和運行時支持.
對新語言(或者說時語言 + 對應的庫 + 該語言的運行時支持)作完整公平的性能比較算是一個龐大考驗.本文對 c# 與 C/C++/D 就開發中都涉及到的基本共通點作初步的性能比較,同時還包括各個語言所帶的庫的性能比較.包括:
1. 運行一次完整的程序(轉載執行一個程序的時間)
2. 算法和遞歸(計算圓周率和 Eratorshnes's sieve )
3. 類型轉換( int->float,float->int,int->string.string->int )
4. 字符串比較
5. 字符串連接
6.( 字符串特定符號統計 )
通過上面的一系列比較,將會揭示 c# 編譯器效力的一些有意思的地方和 .net 運行庫的性能
背景 :
雖然作比較的語言都可以算是 C 語言系列,雖然這五種語言在語法上只有或多或少的不同,但在其他方面,它們的差別是巨大的.(見表一, http://www.digitalmars.com/d/comparison.html ) . 我假設讀者已經熟悉 C/C++/Java ,以及一些基本的 C# .對于 D 語言,不如其他語言那么有名,但你可以找到它的介紹
D 語言介紹
D 語言是 Walter Bright 正在開發的一種語言,安裝和學習這種語言的開銷很小。實質上, D 語言的目標是更成功的 C/C++ 語言,改進包括可變長度的數組,函數參數的 [in][out] 屬性,自動內聯,版本控制,局部函數,內建單元測試,嚴格的類型檢查等等,包括 Java/C# 中實現的忽略指針和引用的差別 , 這在 C++ 中只有通過模板實現。它同時支持對現有 C 函數和 API 的直接支持。
它支持許多跟 Java/C# 類似的特性 , 比如垃圾收集。 D 語言跟 C#/Java 的本質區別是 D 語言運行時不需要運行時的虛擬機支持。 D 語言所支持的所有特性都是在編譯連接時完成的,所以不需要運行時的額外支持。 D 語言的開發目前已經到了 Alpha 版本,將來的成熟版本在絕大多數情況下將會比 Java/C# 高效得多,除非 JIT 優化非常明顯.所以我們才把 D 語言加到我們的測試當中。實際上, Walter 告訴我們, D 完全有能力產生跟 C 代碼運行得一樣快的代碼,同時,由于自動內聯,內建數組,字符串內建優化,非 \0 的字符串結束標記等特性,他相信在 D 語言正式發布的時候,它的性能將會超過 C 。
在真正的測試開始以前,我假定性能按照從高到低排序,應該是 C/C++,D,C#,Java .我跟其隨后證實的其他好多人一樣,一開始就懷疑 C# 產生高性能代碼的能力.
性能比較
我們會在下面接著介紹的十一種不同情況下測試性能.測試平臺是 2G 512M 的 pentium4 ,操作系統是 Windows XP Pro. 測試是在一套我自己設計的工具下進行的,為了符合這篇文章,我理所當然地用 C# 寫了這個工具。在沒有其他干擾進程的情況下,對每個測試者運行七次,分別去掉最快的一次和最慢的一次后得到的品均值,就是測試者的成績。除了下面說到的 noop 測試,所有的測試通過高精確度定時器計算完成所有內循環所需時間完成。(譯者注:比如 int->float 測量中,會通過循環運行多個 int->float 的轉換,然后測量完成整個循環需要的時間)。如 Listing1 所示的代碼,就是用 c# 完成的 f2i 測試。 (C 通過調用 win32 api 函數 QueryPerformanceCounter() ; C++ 通過 WinSTL 的 performance_counter;C# 通過 SynSoft.Performance.PerformanceCounter;D 通過 syncsoft.win32.perf.PerfomanceCounte ; Java 通過 System.currentTimeMillis()) 。每個程序都包含進入實質測試代碼前一段 ” 熱身代碼 ” 來減少程序初始化 ,catch 等因素帶來的不利影響。
編譯器版本號如下:
C#:VC#.net v7.00.9466,.NET Framework v1.0.3705;
D: Digital Mars D compiler Alpha v0.61
Java: J2KDSE 1.4.1
C/C++ 在兩個編譯器下完成,分別是 Digital Mars C/C++ v8.33,STL Port v4.5 和 Inter C/C++ v7.0 ,頭文件和庫用 VC++ 6.0
用 Digital Mars 編譯器的原因是:首先它能快速生成高質量代碼,而且免費。其次是為了和 D 語言作比較,因為 D 語言用到相同的 C/C++ 連接器和很多 C 庫。
為了作徹底的性能比較,所有的編譯器都用了速度最優的優化方案。 Inter 編譯選項是 -QaxW ,它提供兩種代碼路徑,一種是專門為 Pentium4+SIMD 優化,一種是為其他 cpu 優化。具體的代碼運行路徑在運行時自動選擇。 ( 通過對不同 C/C++ 編譯器性能的分析,我認為 Inter 編譯器針對他自己芯片的優化非常好,而對于 Digital Mars 的編譯器編譯出來的代碼性能,可以代表普遍的 C/C++ 性能 )
對這幾種語言作方方面面的比較是不太現實的,因為某些特定的功能并沒有在這幾種語言中都得到實現。比如 C 內建庫里沒有(字符串特定符號統計)函數。不過既然這篇文章的主題是討論 C#, 所做的測試都是 C# 支持的,如果其他語言沒有提供對應的功能,我們會提供一個自定義的實現。同時,如果某種語言對某種功能的內建支持非常差的話,我會考慮提供一個相對好一點的自定義支持。 ( 特別是對于 C/C++ ,至少有一兩個地方看得出效率不高的內建庫掩蓋了語言本身的真實性能 )

簡而言之,我不會列出每種測試在不同語言的具體實現代碼,不過在這里,我會詳細介紹下面幾種測試情況 :
noop.noop 測試的是程序的裝載時間 : 一個空的 main/Main 的執行時間。跟其它測試不一樣,這個測試是在 ptime 工具下完成的 .( 可以從 http://synesis.com.au/r_systools.html) 獲得,它提供了了 unix 環境下 time 工具類似的功能,同時它還能多次運行目標程序來獲得平均值 ( 在我們的測試中, C/C++/D 運行 200 次 ,C# 運行 50 次, java 運行 30 次 ). 為了除去某些特定情況造成的影響,我們會多次測量 ( 譯者注:指多次運行 ptime 程序, ptime 程序自身又會多次運行目標程序 ) ,并且取去掉最高值和最低值后的平均值。
f2i 這個測試把 1 個長度為 10,000 的雙精度浮點值轉換為 32bit 的整數。從 Listing 1 可以看出,使用偽隨機數產生算法可以保證各語言比較相同的浮點數。這帶來了兩個有趣的情況,我們將在查看結果的時候予以討論。

I2f 跟前一個相反,這個測試對針對具體某個函數進行的測試。程序生成 10000 個確定的偽隨機整數,保證在不同語言中被轉換的整數是相同的。
I2str 把整型轉換成成字符串型。整型變量從 0 循環增加到 5000000 ,每次做一個整型到字符串型的轉換 ( 見 listing2).C#/Java 用內建函數完成 ---i.ToString() 和 Integer.Tostring(i),C/D 用傳統的 sprintf() 完成。 C++ 分為兩次完成。第一次用 iostream 的 strstream 類,和預期的一樣,這種情況下的性能非常差。為了提高效率,我采取了一些措施,所以導致了第二種方案,見 i2str2

Str2i. 把字符串轉換成整型。建立一個字符串數組,內容是 i2str 轉換過來的內容,然后再通過語言本身的支持或者庫函數把字符串轉換回去,計算轉換回去的時間(見 listing3 ) .C/D 用 atoi 完成, c++ 用 iostream 的 strstream 完成, c# 用 int32.parse 完成, java 用 integer.parseint() 完成。

picalc 迭代和浮點。通過 listing 4 所示的迭代算法計算圓周率。每種語言的實現其迭代次數是 10,000,000 。

Picalcr. 遞歸。通過 listing5 所示的算法計算圓周率。對每種語言,我們會做 10000 次運算,考慮到 java 堆棧在深層地歸時很容易被耗盡,所以每次運算限制在 4500 次遞歸以內。

Sieve. 疊代。通過 Listing 6 所示的迭代算法( Eratosthenes's sieve ( http://www.math.utah.edu/~alfeld/Eratorthenes.html ))得到質數。每種語言的實現其迭代次數是 10,000,000 。

Strcat. 字符串連接 --- 通常是大量運行時開銷的根源,也是導致程序低效的潛在領域。(我以前做過一個對性能敏感的項目,通過改善效率底下的字符串連接,我獲得了 10% 的整體性能提升) . 測試通過構造四種不同類型的變量 :short,int, double 和一個 string ,然后把它們連接成一個 string 變量完成,連接時添加用分號分割.在連接的時候采用四種不同的連接方
法 :1) 默認連接方法 2) 故意構造出來的導致效率低下的連接方法 3)C 語言種典型的 printf/Format 格式,需要重新分配空間 4) 為提高性能而進行的硬編碼 . 代碼摘錄見 Listing7

Strswtch. 通過級聯 switch 進行字符串比較 . 通過語言支持的 switch 語句進行比較或者通過一些列的 if-else 語句模擬( Listing 8 ).這項測試是為了檢驗 c# 的字符串處理功能是否如他所聲稱的那樣高效.測試分成兩部分 : 首先是進行字符串之間的賦值,也就是同一身份的比較(譯者注:也就是字符串內部的指針直接復制,不涉及到字符串內容的拷貝).其次是通過字符串的拷貝后進行比較,這樣就避免了同一身份比較而是進行進行字符串之間的字面比較.每種語言上都進行 850000 次疊代,因為超過這個次數后會導致 java vm 內存耗盡.
strtok 字符串分段。字符串的運行效率也極低,有時甚至引發嚴重的運行開銷。這個測試( Listing 9)讀取一個文本文件的全部內容至一個字符串,然后拆分成段。第一個變量用字符分隔符:‘;'分隔字符串,并忽略所有的空白。第二個變量也用分號分隔,但保留了空白。第三個和第四個變量用字符串分隔符:<->,并相應的忽略和保留了空白處。C里僅第二個變量用了strtok(),C++四個變量都用STLSoft的string_tokeniser模板,但各自正確給參數賦值。C#第一個變量用System.String.Split(),2至4變量用SynSoft.Text.Tokeniser.Tokenise();Java用java.util.StringTokenizer,僅支持變量2和4(注:我選用STLSoft的string_tokeniser模板不是我要為STLSoft作宣傳,而是測試表明它比Boost的快2.5倍,而Boost是大多數開發人員在查找庫時的首選)。

結果
在 noop測試中,表2列出了以微秒(us)為單位裝載一個空操作的平均開銷。顯然C,C++,D的差別不合邏輯,每一個大約是5-6毫秒,而Intel確低至2ms。我所想到的唯一可能是這個編譯器優化了所有的C實時運行庫的初始化工作。如果是這樣,這個性能在其他非試驗性即實際應用中不能體現出來,因為它至少需要一些C … ,這有待深入研究。

C#用了大約70ms,Java約0.2秒。C#和Java的裝載清晰的表明其實時開銷在于它們的實時運行的結構基礎(VM和支持DLLs)。但是除了大規模的命令行的商業化批處理,對極大型系統(上百萬條代碼)或CGI的基礎構建,啟動開銷在大多數情況下不重要。重荷服務器允許在數秒內啟動,所以語言間200ms的差異就微乎其微了。

其余的結果分三部分顯示。圖 2和3分別展示了字符串連接和分段的結果。圖1包含了其余全部的測試結果,我們現在先來看看它。


圖 1中,對各測試項,C,C++,D,Java的結果均以其相應測試項C#運行時間的百分比顯示。因此,在100%線上下波動的結果說明與C#性能相當。高百分比說明C#性能較優,而低百分比表明C#性能相對較差。
f2i 拋開Intel不談,可以看出C#在浮點到整型的轉換中效率遠遠高于其余四個,僅是C,C++, 和D開銷的2/3,是Java的2/5。在我讓它們使用相同的隨機數發生器前,不同語言的性能差異極大,這說明轉換速度非常依賴具體的浮點數值,當然這是后見之明了。為了確保每個語言做相同的轉換,程序計算轉換值的總和,這也有助于任何過度的優化去除整個循環??梢缘弥@種轉化在語言間正確度不同。每種語言的計算總和迭代10,000次的結果在表3中列處。它們表明(雖然無法證實)C,C++,和D是最正確的,Java其次,C#最差。C#犧牲正確性和降低處理器浮點精度(參見 引用)換取優越性能是令人信服的,但事實上做此判斷前我還需要更多的證據。至于Intel,說它的性能沒有什么更卓越的也是公平的。

5個語言這里效率相當。另4種性能比C#低5%以內。唯一叫人感興趣的是這四個語言都是性能比C#略好。這表明C#確實效率低,雖然在這方面只低一點。這與從整型到浮點型轉換的結果相反。同樣,值得我們注意的是,雖然Java在浮點到整型的轉換中性能很低,但在整型到浮點的轉換中卻是最好的,比C#高10%。對f2i,Intel表現極佳,其C和C++分別只用了C#時間的10%和23%。
i2str 整型到字符型的轉換。 C#比C和D的printf略好。比C++的以iostream為基礎的轉換效率要高。它是C#運行時間的4倍(對Intel 和VC6 STL/CRT是5倍)。但Java性能出色,用了C#2/5的時間。因為C++的效率低下,第二個變量使用STLSoft的 integer_to_string<> 模板函數。它在很多編譯器上比C和C++庫的所有可利用的轉換機制要快。在這種情況下,性能比C#高10%,比C和D高約20%。但是因為Intel編譯器的優化影響, integer_to_string<> 似乎找到了其完美搭檔:其時間比C#低30%,是第三個速度超過Java的,比Iostream快17倍以上。認為它很可能已經接近了轉換效率的上限也是合理的。
str2i 字符型到整型的轉化結果與前面大相徑庭。 C和D用atoi比C#快約5-8倍,比Java快3倍,Java比C#效率高得多。但是這四個比C++以iostream為基礎的轉換都快。C++用 Digital Mars/STLPor運行時間t 是 C#的2.5倍,用 Intel和VC6 CRT/STL 是 10倍。這是iostream的另一個致命弱點。但因此說C++效率低下未免有失公允(實際上,我正在為 STLSoft 庫寫字符至整型的轉換,以和它 integer_to_string<> 的杰出轉換性能相匹配。最初的結果表明它將優于 C的atoi,所以這個轉換中C++也是優于C的。我希望能在本文刊發前完成此轉換。請參閱 http://stlsoft.org/conversion_library.html 。 )
picalc 在這里各語言性能表現相近。 C#和Java幾乎相同,比C,C++效率高上10%。C#的性能可以這樣解釋:它的浮點數操作效率高,這可以從f2I看出。但這不能理解Java的表現。我們可以認為C#和Java都能較好的優化循環,它涉及函數調用,但這有賴于深入研究。既然性能相差10%以內,我們只能得出這樣的結論:各語言在循環結構性能方面差異不顯著。有趣的是,Intel的優化這里沒有實際效果,可能是因為Pi值計算太簡單了吧。
Picalcr : 這里的結果可以更加確定一點, C , C++ , C# 和 D 語言的性能偏差在 2% 之內,我們可以公平地說它們對于遞歸執行的性能是一樣的。 Java 卻是花費比其他四種語言多 20% 的時間,加上 JVM ( Java 虛擬機)在超過 4500 次遞歸時堆棧耗竭,可以明顯地從速度和內存消耗方面得出 Java 處理遞歸不是很好。 Intel 的優化能力在這里有明顯的作用(讓人印象深刻的 10% ),但我在這里并沒有詳細分析這一點。
Sieve: 相對簡單的計算質數的算法(僅包含迭代和數組訪問)說明 C , C++ , C# 和 D 語言在這方面事實上是一樣的(僅 0.5% 的偏差),但 Java 的性能低了 6% 。我認為這中間大有文章, C , C++ 代碼不進行數組邊界安全檢查,而 C# 和 Java 進行數組邊界檢查。我對這種結果的解釋是: C# 和 D 語言在 FOR 循環內能根據對數組邊界的條件測試對邊界檢查進行優化,而 Java 不行。由于 C# 是新興語言, D 語言還沒發行,這就讓人不那么失望了。即使這不是為 Java 辯解,也不會讓人印象深刻了。再說, Intel 優化起了明顯的作用——增長 5% 性能,正如 picalcr 例子,這相對于其他語言更讓人印象深刻。
Strswtch : 這個測試對于 C# 語言不是很好。即使是 C++ 低效的字符串類操作符 = = 都比 C# 的性能快 2.5 倍 (Digital Mars) 到 5 倍( Intel VC6 STL/CRT )。 .NET 環境下字符串是內部處理的,這表示字符串存儲在全局離散表中,副本可以從內存中消除,等值測試是以一致性檢查的方式進行的 ( 只要兩個參數都是在內部 ) 。這明顯表示內部修復機制的效率嚴重低下或者一致性檢查沒有建立 C# 的 ”string-swatching” 機制性。 C# 的這種嚴重低效率更讓人傾向于后者,因為想不到個好的理由解釋為什么會這樣。正如例 8 所示,變量 1 的字符串實例是文字的,都在內部(事實上,這里有個限制沒有在例 8 中表示出來,在變量 1 的預循環中用來驗證參數是真正在內部的)。
當在變量 2 中假裝進行一致性檢查,發現僅 Java 的性能有明顯的提高,從 2% 到令人印象深刻的 30% 。很明顯 Java 在使用一致性檢查所取的性能是不切實際的,因為在真正的程序中不能限制字符串都是文字的。但這可用來說明支持內部處理和提供可編程實現訪問這種機制的語言的一種性能。諷刺的是, C# 作為這五種語言之一,它的性能是最差的。
Strcat : 表 2 以微秒( μs )顯示了五種語言每一種在這個例子中的四個變量的花費時間。本文沒有足夠的篇幅來細節描述 17 執行代碼,但盡量保持例子 7 的四個變量的真實面貌。變量 1 和 2 在所有語言執行代碼中涉及以堆棧為基礎的字符串內存,同時 C , D 語言在第三個變量中、 C , C++ , D 語言在第四個變量中用結構內存,這在某種程度說明他們的性能優于其他變量和語言的執行代碼。本例子的 C , C++ 的討論適合于 Digital Mars 版本,因為很明顯的 Intel 所帶來的性能提高被 Visual C++ 運行時刻庫的效率(非期望)所抵消,事實上 Intel 的每種測試的性能都比 Digital Mars 的差。
第一種測試是以默認的方式執行的,很顯然 C# 和 D 語言的性能都好于其它語言。很奇怪的是 Java 的性能最差,因為 Java 被認為有能力在一條語句中把字符串序列解釋成字符串構件格式(在變量 4 中手工轉換)。
第二種測試,眾所周知它的格式不好,透露了很多東西。它的劣性根據語言而不同,對于 C# , D 和 Java ,包含在連續的表達式中連接每一項,而不是象第一個變量在一個表達式中。對于 C ,它簡單地省略了在開始多項連接重新分配之前的的內存請求。對于 C++ ,它使用 strstream 和插入操作符而不是使用 str::string 和 + ()操作符。結果顯示所有語言不同程度地降低性能。 C 內存分配器的附加輪詢看起來沒有起多大作用,可能因為沒有競爭線程造成內存碎片,所以消耗同樣的內存塊。 C++ 改變成 iostreams 使代碼更緊湊,但沒有轉變數字部分,它們僅被插入到流當中,所預計的執行結果驗證了這一點。 D 因為這個改變降低性能許多( 32% ), Java 更多( 60% )。很奇怪的是 C# 性能在這里僅降低很小,少于 7% 。這讓人印象非常深刻,意味著可能在 .NET 的開發環境下因為 Java 字符串連接而形成的代碼回查的警覺本能會衰退。
第三種測試使用 printf()/Format() 的變量不足為奇。通過使用部分 / 全部的幀內存, C , C++ 和 D 達到很好的性能。奇怪的是 C# 的性能僅是 22% ,簡直不值一提。 Java 根本沒有這個便利功能。(以個人的觀點也是不值一提的) C#和D語言都能夠在封閉的循環中,依照數組邊界測試情況,優化他們的越界檢查。
最大的 測試 -硬編碼的性能-這個是很有趣的問題,因為 C++還是能提供超級的性能,如果能夠依照問題的精確領域來編碼,即使別的語言(C#,java)使用默認的或者性能優化的組件或者理念也是如此。在C#和java上使用他們各自的StringBuilders能夠提供真實的效果,達到他們在這個集合中的最佳性能。然而,C#能發揮最佳效率的機制,還是不能與C/C++、D語言相比。更糟糕的是,java的最佳性能都比別的語言的最差性能要可憐,這是相當可悲的。
總之,我們說對底層直接操作的語言垂手可得地贏得那些轉化率工作得很好的或者完全地在中間代碼中,并且很顯然地能夠提供良好性能的語言用法對java是很重要的而對C#是不太重要的。請注意這一點。
Strtok . Table3顯示了五種語言的每一個環節總共的時間(單位是毫秒ms)。同時他們的空間占用在這篇文章中并沒有提及,但是所有語言的實現都是非常直接的并且很明顯很接近在Listing9中列出的四個問題的本質。與strcat一樣,基于同樣的原因,使用Intel編譯器的結果沒有討論。
第一個問題――分割字符,保留空格――顯示C++版本是明顯的勝利者,D語言要慢20%,C# 要3倍的時間。顯然,這個與庫的特征的關系比語言的更明顯,
并且我使用 tokenizer比著名的Boost更多一些,那樣的話,D語言要比C++快2倍,并且C#只慢20%。雖然如此,STLSoft的tokenizer是免費得到,我想我們應該把這個作為C++的一個勝利。(其實,template在這一點上的使用STL的basic_string作為他的值類型,這個并不因為他的良好性能而聞名,這可能引起爭論-我并沒有使用一個更有效率的string,就像STLSoft的frame_string.總之,我認為這是個公平比較)。
第二個問題--分割字符,去掉空格--包含所有語言的實現版本。自然地,C語言贏得了比賽,它使用了strtok()函數,這個函數在創建tokens時并不分配內存并且直接寫終結符NULL到tokenized string的結尾。盡管有這些不利條件,C++的性能還是很好的,時間是C的221%,比較好的是D語言,432%。
C#和java就很差了,分別是746%和957%。這簡直是不敢相信的,C#和java運行的時間是C++的3.4倍和4.3倍,這三種語言都為tokens分配了內存。我相信這個是STL模型在處理iterable sequences時比passing around arrays更有效率的很好的例子。(請注意,我們有高度的信心這是場公平的關于C++,C#和D語言的比較,我寫了三種tokerizer的實現,都是公開的,可得到的.)
第三個問題--分割句子,保留空格--顯示了兩件事情。第一,在這三種語言在實現這個程序的代價比實現單個字符更昂貴。第二,我們發現D語言取代了C++的最佳性能的地位,領先5%左右。C#繼續被甩下,大概是另外兩種語言的1.8倍時間左右。
第四個問題--分割句子,去掉空格--沒有任何令人意外的,C++和D擁有大概一致的性能。C#比較不錯(大概2倍的性能差距)java更慢(差不多3倍時間)。對于C++,C#和D來說去掉空格比不去掉空格導致一小點的性能損失。很明顯的,在C#中,這么做比其他兩個的損失更大一點。很有價值的是,這些功能在C#和D中是已經實現的。由于數組在C#中是不可變的,從tokens中返回一個沒有空格的數組將導致重新分配一個數組。可是,D允許指派數組的長度,這個能動態地調整數組的大小這是個很好的例子說明D語言提供更好的效率。
總的來說,考慮到性能和自由度,我們能夠說 C++是勝利者,D是很接近的第二名。C擁有最佳的性能,但是只支持一種類型的tokerization。C#表現得很差,java更甚。
總結
我們可以把結果分成3種情況:這些包括語言的特性,包括庫的特性,或者兩者都相關。這些只與語言相關的特性顯示在 f2i, i2f, picalc, picalcr, and sieve scenarios,這些在語言選擇上的作用是很小的。c??雌饋碓诳傮w上是最好的,但是有點讓人不能信服的是它在f2i中取得的優異性能并且是因為犧牲了浮點精確度而取得效率。(這一點需要更多大檢查,我將在下一篇文章中去做。)
java是明顯最差的 。
當討論intel編譯器的浮點計算的深度優化效果時,它很明顯地顯示,c和c++能夠做的比C#更好。我討論這些并不是說這些語言必須要更好的效率,而是說他們能比其他的語言提供更好的優化和增強的機會。這個大概是與本主題完全不相干的是,C和C++比別的語言有更多的編譯器;更多的競爭,更聰明的人擁有不同的策略和技術。編譯器的結果的運行目標是處理器,這將導致戲劇性的效果;我們僅僅在表面上探討了這個龐大的問題,但是我不久將在以后的文章中回到這個主題。
Strtok 是唯一一個能說是library-only的,在這一點上C#干的并不好。雖然比java快,但是比其他的語言慢2倍或更多。同樣令人失望的是VisualC/C++的運行庫。在這一點上包括語言和庫的效果的是i2str,str2i,strcat和strtswtch,描繪的并不是很清楚。C#明顯的在string的拼接這一環節上比java好得多,但是明顯地比其他的差。關于尊敬的C,C++,D,只是在一些環節上領先,在另外一些環節上相當地差。
很有趣的是定制的C++的庫替換加上深度優化的intel的編譯器。
總之,不可能做出定量的結論。從語言的表層來看, C# 和 Java 挺簡單的,但低效的庫降低了速度。我覺得,當相關的庫還不如語言本身的時候, C# 的確實有微弱的優勢;但反過來,當庫逐漸完善,超過語言本身后, C# 明顯就不行了。但還是能看到一點點希望: C# 還是有可能實現預期的目標,因為庫比語言更容易實現組裝。但是,人們認為由于跟別的語言相比, C# 和 Java 所用的庫與語言結合的更加緊密,這些庫就可以作為這兩種語言效率的關鍵所在,至少對當前的版本來說是這樣的。
正如我所作出的示范(從整數到字符)以及相關提到的(從字符到整數),有人可能會批評者并不符合這篇文章的原意。
更深入的探討
本文主要探討了一下 C# 對 C++ 和 Java 可能造成的“威脅”。總的來說,這結果雖然不怎么鼓舞人心,卻足以讓人吃驚。從效率上看, C# 跟 C 以及 C++ 這些過去的同類產品(假如它們是更高效的)相比只能算是一般水平,至少在基本語言特征的比較上是這樣的。從某種程度上來說, Java 也是如此。(我承認我無法證明 C 和 C++ 相對于 C# 的絕對優勢。在多年前我的畢業論文分析調查期間,我認識到令人乏味的結果正和令人興奮的結果一樣生動形象,但前者花費更小的經濟支出。)
上述結論對于多處理器以及(高性能的文件/網絡處理系統)等來說卻不一定適用。不過,這確實很好地展現出了這些語言的基本效率以及更復雜的運算所依賴的最基本的庫。
就 D 發展狀況來看,現在就對它進行這樣的歸類似乎為時過早,不過它現在的表現確實不錯。 D 的效率至少是 C# 的 167% ,其實大多數情況下還不止。有些人指出,目前僅僅還處于字符時代,只需幾個人就能完成一個編譯器及其附帶的庫。我猜測, D 有可能發展成一門強大的技術。
就我個人而言,作為一個對 C# 持有偏見的人,偶然還是會對它的性能感到驚訝。我熱切地期盼著能將性能的對比分析延伸到更多領域:復雜而高效的內存的應用,多線程,進程通信,特殊文件處理,手寫代碼開銷,這些技術都能讓我們對語言進行更深入的研究。能看到 Inter 公司怎樣通過定制的 C Library (比如 CRunTiny ;參考 http://cruntiny.org/ )和 STL (比如 STLPort )。
鳴謝
感謝 Walter Bright 為我提供了一份言簡意賅的關于 D 的介紹,并及時地指出了一些當時被我忽略的優點,使我能對測試工程進行改進。同時也要感謝 Sun Microsystems 公司的 Gary Pennington ,他為我提供了關于 Java 方面的資料。還有 Scott Patterson ,他對本文的草稿進行了大量的精簡,并對全篇文章進行了細致的檢查。
]]>
posted on 2005-09-15 10:23
Sung 閱讀(6619)
評論(3) 編輯 收藏 所屬分類:
software Development