本文版權歸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