MSDN 線上教學——使用 C#: 開啟包裝! 快點! (轉)

gugu99發表於2008-05-04
MSDN 線上教學——使用 C#: 開啟包裝! 快點! (轉)[@more@]MSDN 線上教學——使用 : 開啟包裝! 快點!

.com/code/default.?URL=/code/sample.asp?url=/MSDN-FILES/026/002/254/msdncompositedoc.">請訪問 MSDN 中心,本專欄文章中示例的原始碼(英文)。

上個月,我們介紹了裝箱和取消裝箱的方法,以及什麼時候會用到它們。這個月,我們將研究裝箱對的影響,以及我們應當怎樣將這種影響減少到最小。

裝箱和效能

由於進行了裝箱,所以 C# 中的模型非常簡單明瞭。但是採用經過裝箱的數值型別會導致效能的降低。在大多數情況下,物件模型的簡化比效能的降低更為重要。對於一般的而言的確是這樣。節省開發和維護軟體的時間是需要進行的地方,同時正是這些最佳化措施能夠最大程度地改善的效能。

最佳的解決方案可能是使用一個通用的 ArrayList。這樣我們可以宣告一個 Arraylist,該物件就可以不經過裝箱直接 int 值。不幸的是,在 C# 的第一版本中沒有通用資料型別(儘管人們正研究在後續版本中提供這種功能),所以這種解決方案於事無補。

有時候可以用別的沒有裝箱損耗的物件來代替。除了利用 ArrayList 來儲存 int 值以外,還可以(需要使用較少的額外程式碼來新增新的元素)使用 int[],或者還可以編寫一個 ArrayListInt 來封裝這些額外程式碼。但是,這並不是最簡便的解決方案。

還有很多其他的選擇。為了更好地對它們進行說明需要舉一個具體的例項。

字數統計

這是我以前編寫的一個 C# 程式,它可以將一個測試分解成一個個的單詞,再統計每個單詞出現的次數。我寫這個程式的目的是為了瞭解常用類在中的工作方式,並將用 C# 編寫的程式與用 編寫的類似程式進行比較。

如果您還沒有接觸過 Perl 的話, 這是一種專門用於文字處理的語言,所以特別適用於這樣的任務。

最容易想到的、統計單詞出現次數的方法是將單詞作為關鍵字,利用“雜湊表”儲存單詞出現的次數。程式碼的內迴圈如下所示:

while ((line = stream.ReadLine()) != null) { foreach (string in regexSplit.Split(line.ToLower())) { int count = 0; value = wordTable[word]; if (value != null) count = (int) value; wordTable[word] = count + 1; } }


在內迴圈中, 我們從“雜湊表”中獲得某個關鍵字的當前值。如果該值不是空值,就將它轉換成一個 int。隨後重新將正確的值儲存到“雜湊表”中。

編寫這段程式碼很容易,但是如果某個單詞已經存在的話,它將會造成相當大的效能損耗。僅僅為了累加某個值,我們不僅要對它進行解裝箱,還需要為每個字串計算兩次雜湊碼。

儘管會產生這些損耗,但是這段程式的效能仍然是相當不錯的。為了獲得一些具體的效能指標,我需要使用一些比較合適的文字檔案。我首先下載了由 David er 撰寫的(英文),其中包括 160,000 個單詞。對於一次比較全面的測試而言這個檔案顯得有些小。用 Perl 編寫的程式可以在不到一秒鐘的時間裡將它處理完畢。隨後我從 (英文)下載了/cgi-promo/pg/t9.cgi?entry=2600&full=yes&site=ftp://ibiblio.org/pub/docs/books/gutenberg/">《戰爭與和平》(英文),其中包括大約 600,000 個單詞。這個要稍好一些。

用 Perl 編寫的程式花費了大約 4 秒鐘,就完成了對《戰爭與和平》的字數統計。而使用了被裝箱的 int 的 C# 程式花了大約 10 秒鐘。每秒鐘處理 60,000 個單詞已經很不錯了,但是我對到底可以將它的速度提高到多少非常感興趣。

確定基準

在我們開始嘗試不同的方法以前,我們需要知道這個 C# 程式的基準時間是多少。換句話說,讀取檔案的所有行,並將它們分解成一個個的單詞而不進行字數統計,這個過程需要多長時間。這樣做非常重要,因為只有這樣我們才能只比較原始碼中的統計部分所用的時間。

如果我們編寫實現上述任務的程式碼,我們發現在大約 7 秒鐘的時間內就完成了除了字數統計以外的所有任務。這意味著字數統計部分花費了幾秒鐘。

讓程式執行得更快

我們的目標是取消整型資料的裝箱和取消裝箱過程。換句話說,我們希望能夠不經過對整型資料進行解裝箱以及隨後再對其重新裝箱的過程直接累加“雜湊表”中的值。要做到這一點,一種顯而易見的方法是使用一種引用型別,而不是數值型別。我們可以將整型統計值裝箱在類中。這個類非常簡單:

class IntHolderClass { int count; public IntHolderClass() { count = 1; } public int Count { get { return(count); } set { count = value; } } public overr string ToString() { return(count.ToString()); } }


當為這個類建立一個新的例項時,統計值設為 1。統計值可以透過 Count 屬性遞增。主迴圈被修改成如下形式:

foreach (string word in regexSplit.Split(line.ToLower())) { wordCount++; IntHolderClass value = (IntHolderClass) wordTable[word]; if (value == null) { wordTable[word] = new IntHolderClass(); } else value.Count++; } }


如果單詞不存在,就建立一個新的裝箱類例項,並將其放入“雜湊表”中。如果單詞已存在,就累加其計數。

當我們執行此版本時,我們發現所花時間不到一秒鐘。這大約是採用了裝箱的程式所花時間的 30%。剛開始我對這個結果有些驚訝,因為表面上看起使用類應當造成更多的損耗。但是經過仔細思考,我們知道,很顯然,儘管建立一個被裝箱的 int 與建立一個裝箱類所造成的損耗是相同的,但是對於被裝箱的 int,我們為每個單詞(總共有 600,000個)而不是為每個唯一的單詞(大約有 19,000 個)建立了一個裝箱。

這種技術所帶來的效能改善的程度取決於我們在數值儲存在集合類中時所的操作的次數。如果我們只是找到該物件並將其取出,那麼不會帶來任何效能上的改善。例如,如果我用一個 ArrayList 來儲存用於後續處理的整型值,那麼採用這種技術對於效能將沒有任何幫助。

當您編寫程式碼時,最好使用盡可能簡便的方法。在實現這種方法以後,如果需要更快的速度,則可以再考慮其他的方法來提高執行速度。

另一種技術

還有一種類似的方法可獲得同樣的結果。數值型別可以作為介面,但是因為介面都是引用型別,所以您只能使用一個引用某個被裝箱的數值型別的介面。我們可以定義一個具有 Increment() 成員的介面,並使用該數值型別實現它。利用這種方法,我們可以直接透過被裝箱的數值型別獲得介面,然後再 Increment() ,這一切都不需要取消裝箱。介面和數值型別如下:

interface IIncrement { void Increment(); } struct IntHolderStruct: IIncrement { int value; public IntHolderStruct(int value) { this.value = value; } public int Value { get { return(value); } } public void Increment() { value++; } public override string ToString() { return(value.ToString()); } }


我很希望宣佈這是由我自己獨立完成的,但實際上它是我在最近的一次會議上遇到的一個與會者編寫的。這種實現的主迴圈的工作方式與採用裝箱類的那段程式非常相似。這個程式執行時所花費的時間大約是採用被裝箱的 int 的程式所花時間的 32%。換句話說,它只比採用裝箱類的程式稍慢一點。

全部結果

以下內容是對全部結果的總結。

表 1《On Basilisk Station》的結果

實現方式 所用時間 相對於被裝箱 Int 的比例 被裝箱 Int 0.64 1.00 裝箱類 0.28 0.43 裝箱結構和介面 0.29 0.45

表 2《戰爭與和平》的結果

實現方式 所用時間 相對於被裝箱 Int 的比例 被裝箱 Int 3.01 1.00 裝箱類 0.92 0.31 裝箱結構和介面 0.97 0.32

各種實現和程式程式碼可以在示例程式碼中找到。為了在歸檔時不至於太大,我在這裡省去了這些文字檔案。我還引用了一個 Perl 檔案作為參考。如果您要執行這個 Perl 程式,您需要從 站點下載 ActivePerl 軟體(這是一個免費軟體)。

總結

我希望您喜歡這篇關於裝箱的介紹。通常,儘管裝箱會導致效能上的一些損耗,但是並沒有太大關係,因為相比之下簡化更為重要。但是有時候,可能需要使用一個裝箱類來減少由於裝箱所帶來的效能損耗。

網站摸彩袋

在這個摸彩袋中有相當多的站點,所以我使用我的秘密的隨機數生成器來挑出其中的五個。它們是:

  • free.com/">CSharpFree.com





  • Enhance Project

再次需要指出的是,這些都是沒有保證的站點。我所能保證的是當您點選上面的 URL 地址時會顯示一個網頁。

 


Eric Gunnerson 是 C# 組的 QA 領導,C# 設計組的成員,以及“A Programmer's Introduction to C#”(英文)一書的作者。他從事程式設計工作的時間很長,他甚至知道什麼是 8 英寸,還能用一隻手裝磁帶。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1003287/,如需轉載,請註明出處,否則將追究法律責任。

相關文章