程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> 運用 C#: 打開包裝! 快點!

運用 C#: 打開包裝! 快點!

編輯:C#基礎知識
  請訪問 MSDN 源代碼中心,下載本專欄文章中示例的源代碼(英文)http://msdn.microsoft.com/code/default.asp?URL=/code/sample.asp?url=/MSDN-FILES/026/002/254/msdncompositedoc.xml。
  
  上個月,我們介紹了裝箱和取消裝箱的方法,以及什麼時候會用到它們。這個月,我們將研究裝箱對性能的影響,以及我們應當怎樣將這種影響減少到最小。
  
  裝箱和性能
  由於進行了裝箱,所以 C# 中的對象模型非常簡單明了。但是采用經過裝箱的數值類型會導致性能的降低。在大多數情況下,對象模型的簡化比性能的降低更為重要。對於一般的軟件而言的確是這樣。節省開發和維護軟件的時間是最需要進行優化的地方,同時正是這些優化措施能夠最大程度地改善程序的性能。
  
  最佳的解決方案可能是使用一個通用的 ArrayList。這樣我們可以聲明一個 Arraylist<int>,該對象就可以不經過裝箱直接存儲 int 值。不幸的是,在 C# 的第一版本中沒有通用數據類型(盡管人們正研究在後續版本中提供這種功能),所以這種解決方案於事無補。
  
  有時候可以用別的沒有裝箱損耗的對象來代替。除了利用 ArrayList 來保存 int 值以外,還可以(需要使用較少的額外代碼來添加新的元素)使用 int[],或者還可以編寫一個 ArrayListInt 來封裝這些額外代碼。但是,這並不是最簡便的解決方案。
  
  還有很多其他的選擇。為了更好地對它們進行說明需要舉一個具體的實例。
  
  字數統計
  這是我以前編寫的一個 C# 程序,它可以將一個測試文件分解成一個個的單詞,再統計每個單詞出現的次數。我寫這個程序的目的是為了了解常用表達式類在框架中的工作方式,並將用 C# 編寫的程序與用 Perl 編寫的類似程序進行比較。
  
  如果您還沒有接觸過 Perl 的話, 這是一種專門用於文本處理的編程語言,所以特別適用於這樣的任務。
  
  最容易想到的、統計單詞出現次數的方法是將單詞作為關鍵字,利用“散列表”存儲單詞出現的次數。代碼的內循環如下所示:
  
   while ((line = stream.ReadLine()) != null)
   {
   foreach (string word in regexSplit.Split(line.ToLower()))
   {
   int count = 0;
   object value = wordTable[word];
   if (value != null)
   count = (int) value;
   wordTable[word] = count + 1;
   }
   }
  
  在內循環中, 我們從“散列表”中獲得某個關鍵字的當前值。如果該值不是空值,就將它轉換成一個 int。隨後重新將正確的值存儲到“散列表”中。
  
  編寫這段代碼很容易,但是如果某個單詞已經存在的話,它將會造成相當大的性能損耗。僅僅為了累加某個值,我們不僅要對它進行解裝箱,還需要為每個字符串計算兩次散列碼。
  
  盡管會產生這些損耗,但是這段程序的性能仍然是相當不錯的。為了獲得一些具體的性能指標,我需要使用一些比較合適的文本文件。我首先下載了由 David Weber 撰寫的《On Basilisk Station》(英文),其中包括 160,000 個單詞。對於一次比較全面的測試而言這個文件顯得有些小。用 Perl 編寫的程序可以在不到一秒鐘的時間裡將它處理完畢。隨後我從 Project 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 override string ToString()
   {
   return(count.ToString());
   }
  }
  
  當為這個類創建一個新的實例時,統計值設為 1。統計值可以通過 Count 屬性遞增。主循環被修改成如下形式:
  
   fore

[1] [2] [3] 下一頁

ach (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 程序,您需要從 http://www.activestate.com 站點下載 ActivePerl 軟件(這是一個免費軟件)。
  
  總結
  我希望您喜歡這篇關於裝箱的介紹。通常,盡管裝箱會導致性能上的一些損耗,但是並沒有太大關系,因為相比之下簡化更為重要。但是有時候,可能需要使用一個裝箱類來減少由於裝箱所帶來的性能損耗。
  
  網站摸彩袋
  在這個摸彩袋中有相當多的站點,所以我使用我的秘密的隨機數生成器來挑出其中的五個。它們是:
  
  CSharpFree.com
  
  
  csharpindex.com
  
  
  C# Corner
  
  
  The .NET Enhance

上一頁  [1] [2] [3] 下一頁

Project
  
  
  Technical Lead
  再次需要指出的是,這些都是沒有保證的站點。我所能保證的是當您點擊上面的 URL 地址時會顯示一個網頁。
  
  
  
  
  --------------------------------------------------------------------------------
  
  Eric Gunnerson 是 C# 編譯器組的 QA 領導,C# 設計組的成員,以及“A Programmer's Introduction to C#”(英文)一書的作者。他從事編程工作的時間很長,他甚至知道什麼是 8 英寸磁盤,還能用一只手裝磁帶。  

上一頁  [1] [2] [3] 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved