不同Framework下StringBuilder和String的效能對比,及不同Framework效能比(附Demo)

sinodzh發表於2014-12-09

本文版權歸mephisto和部落格園共有,歡迎轉載,但須保留此段宣告,並給出原文連結,謝謝合作。

文章是哥(mephisto)寫的,SourceLink

閱讀目錄

本文版權歸mephisto和部落格園共有,歡迎轉載,但須保留此段宣告,並給出原文連結,謝謝合作。

文章是哥(mephisto)寫的,SourceLink

 

介紹

    自己對String和StringBuilder的處理機制略懂,大膽的設想下兩者的效能對比會出現什麼樣的令人意外的情況。於是便有此文,結果我也不知道,那麼我們就根據設計的測試用例來看看現象吧。本文耗時5個小時(包括測試用例),希望大家給點汗水的推薦。

環境搭建

一:搭建多Framework測試解決方案

  由於要測試多個Framework下的效能,所以就建立了個解決方案,版本包括:2.0,3.0,3.5,4.0,4.5。這麼多的工程,測試起來也比較麻煩,俺還是耐心的進行了測試。

  IDE採用的VS2013,所以大家要下載後自己執行Demo,還是要注意下IDE版本,如是低版本,自己將4.5的移除即可

二:主要類及方法介紹

1:TestUtil類

  用於String和StringBuilder的建立的工具類

  1.1:CreateString()

  建立String方法

 1         public string CreateString(int num, string value)
 2         {
 3             string temp = string.Empty;
 4             while (num > 0)
 5             {
 6                 temp += value;
 7                 num--;
 8             }
 9 
10             return temp;
11         }

  1.2:CreateStringBuilder()

  建立StringBuilder方法

 1         public string CreateStringBuilder(int num, string value)
 2         {
 3             StringBuilder sb = new StringBuilder();
 4             while (num > 0)
 5             {
 6                 sb.Append(value);
 7                 num--;
 8             }
 9 
10             return sb.ToString();
11         }

  2:Test類

  2.1:RunStringMinInF2()

  執行String最小次數測試

 1         private static readonly int runCount = 10000000;
 2         public static void RunStringMinInF2(TestUtil testUtil, string word)
 3         {
 4             DateTime date1 = DateTime.Now;
 5 
 6             int i = runCount * 5;
 7             while (i > 0)
 8             {
 9                 testUtil.CreateString(2, word);
10                 i--;
11             }
12             DateTime date2 = DateTime.Now;
13             Console.WriteLine("stringMin:" + (date2 - date1).TotalMilliseconds.ToString());
14         }

  2.2:RunSBMinInF2()

  執行StringBuilder最小次數測試

 1         public static void RunSBMinInF2(TestUtil testUtil, string word)
 2         {
 3             DateTime date1 = DateTime.Now;
 4 
 5             int i = runCount * 5;
 6             while (i > 0)
 7             {
 8                 testUtil.CreateStringBuilder(2, word);
 9                 i--;
10             }
11             DateTime date2 = DateTime.Now;
12             Console.WriteLine("stringbuilderMin:" + (date2 - date1).TotalMilliseconds.ToString());
13         }

  2.3:RunString10InF2()

  執行單個String實體呼叫10次相加測試

 1         public static void RunString10InF2(TestUtil testUtil, string word)
 2         {
 3             DateTime date1 = DateTime.Now;
 4 
 5             int i = runCount;
 6             while (i > 0)
 7             {
 8                 testUtil.CreateString(10, word);
 9                 i--;
10             }
11             DateTime date2 = DateTime.Now;
12             Console.WriteLine("string10:" + (date2 - date1).TotalMilliseconds.ToString());
13         }

  2.4:RunSB10InF2()

  執行單個StringBuilder實體呼叫10次相加測試

 1         public static void RunSB10InF2(TestUtil testUtil, string word)
 2         {
 3             DateTime date1 = DateTime.Now;
 4 
 5             int i = runCount;
 6             while (i > 0)
 7             {
 8                 testUtil.CreateStringBuilder(10, word);
 9                 i--;
10             }
11             DateTime date2 = DateTime.Now;
12             Console.WriteLine("stringbuilder10:" + (date2 - date1).TotalMilliseconds.ToString());
13         }

測試用例

一:在Framework2.0下最少欄位數相加

  測試字串:“a”

  單個例項測試資料次數:2

  被呼叫總次數:10000000

  測試程式碼如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             TestUtil testUtil = new TestUtil();
 6 
 7             Test.RunStringMinInF2(testUtil, "a");
 8             Test.RunSBMinInF2(testUtil, "a");
 9             Test.RunSBMinInF2(testUtil, "a");
10             Test.RunStringMinInF2(testUtil, "a");
11 
12             Console.Read();
13         }
14     }

  防止由於先後呼叫原因,所以測試程式碼中同一個方法出現2次。多次執行,基本上結果差不多,同一次執行中也有資料相差的,但差別很小(幾十ms)。

  執行截圖如下:

  

  結果中在這樣極端的考驗StringBuilder效能的條件下,StringBuilder果然敗下陣來。

 二:在Framework2.0下最少欄位數相加(字串增加)

  測試字串:“abcdefghijklmn1234567890opqrst~!@#^&*()”

  其他測試步驟:同測試方案一

  測試程式碼如下:

 1         static void Main(string[] args)
 2         {
 3             TestUtil testUtil = new TestUtil();
 4 
 5             Test.RunStringMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()");
 6             Test.RunSBMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()");
 7             Test.RunSBMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()");
 8             Test.RunStringMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()");
 9 
10             Console.Read();
11         }

  執行截圖如下:

  

  很顯然字串增加了,對兩者的處理時間都增加了,StringBuilder還是必然的敗在陣來。

  三:在多Framework下最少欄位數相加

  其他測試步驟:同測試方案一

  測試結果如下:

  

  發現在4.0和4.5的版本中StringBuilder是比原來的版本有一定優勢的,在StringBuilder上4.0和4.5優勢都很大,在String上4.5較其他版本略微有點優勢。看來微軟也是在進步啊,大家是不是很期待開源了的Framework,很多牛人可以對其進行貢獻,那效能不是又進步了。

  四:在多Framework下最少欄位數相加(字串增加)

  其他測試步驟:同測試方案二

  測試結果如下:

  

  幾個Framework版本結果差不多,但是在StringBuilder上4.0和4.5敗在陣來,這是為什麼列,大家思考一下,稍會作答

  五:在Framework2.0下10欄位數相加

  測試字串:“a”

  單個例項測試資料次數:10

  被呼叫總次數:10000000

  測試程式碼如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             TestUtil testUtil = new TestUtil();
 6 
 7             Test.RunString10InF2(testUtil, "a");
 8             Test.RunSB10InF2(testUtil, "a");
 9             Test.RunSB10InF2(testUtil, "a");
10             Test.RunString10InF2(testUtil, "a");
11 
12             Console.Read();
13         }
14     }

  測試結果如下:

  對單個例項呼叫次數增加,StringBuilder的優勢就來了,漸漸的String就只能當吊車尾了。

  六:在Framework2.0下10欄位數相加(字串增加)

  測試字串:“abcdefghijklmn1234567890opqrst~!@#^&*()”

  其他測試步驟:同測試方案五

  測試結果如下:

  這個測試很出乎意料啊,跟測試方案二,string的效能差別這麼多,StringBuilder沒什麼差別,大家也可以思考下。

  

  七:在多Framework下10欄位數相加

  其他測試步驟:同測試方案五

  測試結果如下:

  

  在眾多Framework中,4.5和4.0拖妥妥的排在前2位。

  

  八:在多Framework下10欄位數相加(字串增加)

  其他測試步驟:同測試方案六

  測試結果如下:

  

  執行效能都差不多,區別不大。

MSDN說明

一:String和 StringBuilder的型別

  雖然 StringBuilder 和 String 兩個表示字元序列,但它們以不同的方式實現。  String   是不可變的型別。  即看似修改 String 物件的每個操作實際建立新的字串。

  對執行廣泛的字串操作例項 (如修改迴圈中的字串) ,修改字串能重複精確嚴重的效能損失。  方法是使用 StringBuilder,它是易失的字串選件類。  可變性意味著,一旦選件類的例項建立的,則可以將其追加,取消,替換或插入字元修改。  StringBuilder   物件維護緩衝區容納擴充套件到該字串。  如果有足夠的空間,新資料將被追加到緩衝區;否則,將分配一個新的、更大的緩衝區,原始緩衝區中的資料被複制到新的緩衝區,然後將新資料追加到新的緩衝區。

  雖然 StringBuilder 選件類比 String 選件類通常提供更好的效能,當不應使用 StringBuilder 自動替換 String 時,就要操作字串。  效能取決於該字串的大小,對新的字串將分配的記憶體量,您的系統 app 的執行和操作的型別。  您應準備測試您的應用程式確定 StringBuilder 實際上是否可顯著提高效能。

  

在這些條件下考慮使用 String 選件類:

  • 當您的應用程式將對字串更改的數量很小。  在這些情況下,StringBuilder 不可能提供在 String的忽略或效能改進。 

  • 當執行串聯運算的內建的數字,尤其是對於字串文字。  在這種情況下,編譯器可能將串聯運算到單個操作。 

  • 當您生成字串時,當您必須執行廣泛的搜尋操作。  StringBuilder   選件類沒有搜尋方法 ,如 IndexOf 或 StartsWith。  您必須轉換為 String 的 StringBuilder 物件這些操作的,這樣,可以對從使用 StringBuilder的效能。  有關詳細資訊,請參閱 搜尋在 StringBuilder 物件的文字 部分。 

在這些條件下考慮使用 StringBuilder 選件類:

  • 當您希望您的應用程式建立一個未知的設定為字串的更改在設計時 (例如,當您使用迴圈連線包含使用者輸入的隨機數字符串)。

  • 當您希望您的應用程式建立一個大量為字串的更改。

  StringBuilder    物件的預設值容量為 16 個字元。

我的理解

  雖然String是引用型別,但是MS處理的時候,比如“+=”的運算子處理String的時候,是重新申請一塊託管堆,用來儲存處理後的新String,這樣新String的HashCode(地址)也會隨著變化,畢竟是一個新的地址引用,所以表面上很像是個“值型別”。

  因此,MS為了處理Sring帶來的潛在效能問題,就加入了StringBuilder,當然我們這裡自只用到了StringBuilder很少一部分功能(字串相加),StringBuilder還有很多其他效能很好的功能(這裡不表)。個人StringBuilder肯定比String複雜,初始化應該是要慢一點,確實我們在上面測試中的用例1,3告訴我們,兩者例項化差距還是很大的。在處理多個字串相加的時候,效能上還是StringBuilder要強些我的理解是:StringBulider的對面預設值容量是16個字元(Framework4.5),所以當處理的字串大於16的時候,就會重新申請開闢另一塊當前StringBuilder字元長度,(每當追加操作導致 StringBuilder 物件的長度超過其容量,其現有的容量翻倍),這是佔一定的效能損耗的,而String每次都是申請一塊託管堆,所以StringBuilder在多字串處理的時候效能應該是大於等於String(不考慮內部演算法邏輯,只是猜測)

  在不同的Framework中,StringBuilder的效能有時候很快,有時候很慢,這是可以理解的,畢竟設計一個類是有使用場景的,而已StringBuilder也提供了修改預設容量的方法,只是我們在使用的時候沒有根據現實場景對這個進行設定。所以極小的字串相加處理,就可以直接用String來用,但是如果非常頻繁的處理,比如webApi被呼叫,那麼是不是考慮用StringBuilder來,並且,通過測試來預估StringBuilder的預設容量範圍,這樣來達到效能的極致。

  在不同的Framework中,2.0比1.0是有個革命性的變化,比如泛型等等。3.0和3.5加了很多功能,比如WPF,WCF,WFF,Linq等,但他們只是在2.0的基礎上做了擴充套件,豐富,實際底層改變不大,大家從3.0和3.5的安裝版就可以看出,他們都是100多M,都是通過打補丁的方式來弄的,所以安裝包很大。到了4.0,Framework發生的大變化,個人覺得最實質的變化就是引入了System.Core。並且把所有的都重新實現了一遍,所以安裝版只有40多M,加了這麼多東西,從28->40很是可以的。4.5沒有研究,但從4.5引用的還是4.0的System.Core,就應該是底層變化不大,如果大家有知道,瞭解的,希望告訴下我們。所以上面結果中,有時候4.0,4.5領先很多,在有的時候落後很多,也沒什麼的,估計只是策略變了而已。

  Framework也開源了,大家都可以去關注下,我也會抽時間去研究研究,看什麼時候能在將來的Framework中看到自己提交的程式碼,那是多麼愉悅的事情啊,多謝了github這個分散式版本管理,我們就可以修改提交了,稽核過不過無所謂,畢竟提交過,哈哈。如果有一天能稽核過,那可以面試的時候或者跟猿們聊天的時候,多BB啊。

  一寫就到了23:30了,明天還要上班,收筆把,還準備把反射出的不同版本的String和StringBuilder的程式碼貼出來的,打住了,身體也很重要。

Demo下載

原始碼下載

本文版權歸mephisto和部落格園共有,歡迎轉載,但須保留此段宣告,並給出原文連結,謝謝合作。

文章是哥(mephisto)寫的,SourceLink

相關文章