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








