︿
Top

2014年8月16日 星期六

C# String: String.Empty is more efficient than "" ?

緣起

關於字串變數的初始化, String.Empty, "", null 在網路上已經有很多討論, 但大多文字描述, 我想, 還是補上一些圖, 這樣對於細節的釐清, 會比較有幫助; 另外, 不少認為 String.Empty 在效能上比較好, 但這是真的嗎? 本文茲就以下內容作討論:
1. String.Empty, "", null 的說明
2. String.Empty 與 "" 的效能實測
3. 實測結果追蹤




String.Empty, "", null 的說明

思考一下, 下列的程式段, 究竟代表什麼意義.

string str1 = String.Empty;
string str2 = "";
string str3 = null;

以下圖作呈現.



(1) string str1 = String.Empty;
經查 MSDN 的說明, 其實它是一個公用的靜態變數, 所以將它指派給 str1 時, 其實是將其指向一個 "" 的指標內容, 複製給 str1; 所以代表 String.Empty 與 str1 都指向同一個記憶體空間.
public static readonly string Empty
(2) string str2 = "";
這個很明顯, 就是一個指向 "" (位址為 0x3B) 的變數
(3) string str3 = null;
這個也很明顥, 其實就是接地, 沒有指向任何物件

String.Empty 與 "" 的效能實測

參考 Level Up 的 [Performance][C#]String.Empty V.S “” 撰寫以下測試程式, 並提供測試結果.


const int COUNT = 10000000;    //一千萬次

//測試 String.Empty 的效能
static void EmptyStringPerformance()
{
 string str = String.Empty;
 Console.WriteLine(string.Format(@"String.IsInterned(str) = {0}", String.IsInterned(str) != null));
 Stopwatch sw = Stopwatch.StartNew();
 for (int i = 0; i < COUNT; ++i)
 {
  str = string.Empty;    //這裡採用 String.Empty
 }
 sw.Stop();
 Console.WriteLine("Elapsed time for Sting.Empty is {0} raw ticks ", sw.ElapsedTicks);
 Console.WriteLine("Elapsed time for Sting.Empty is {0} ms ", sw.ElapsedMilliseconds);
}

//測試 "" 的效能
static void QuotedStringPerformance()
{
 string str = "";
 Console.WriteLine(string.Format(@"String.IsInterned(str) = {0}", String.IsInterned(str) != null));
 Stopwatch sw = Stopwatch.StartNew();
 for (int i = 0; i < COUNT; ++i)
 {
  str = "";      //這裡採用空字串 ""
 }
 sw.Stop();
 Console.WriteLine("Elapsed time for \"\" is {0} raw ticks ", sw.ElapsedTicks);
 Console.WriteLine("Elapsed time for \"\" is {0} ms ", sw.ElapsedMilliseconds);
}

static void Main(string[] args)
{

    Console.ForegroundColor = ConsoleColor.White;
    Console.WriteLine("========= EmptyStringPerformance ==========");
    EmptyStringPerformance();

    Console.ForegroundColor = ConsoleColor.Gray;
    Console.WriteLine("========= QuotedStringPerformance ==========");
    QuotedStringPerformance();
}
            



各位有沒有發現, 兩者在效能上沒有太大的差異; 甚至 "" 比 String.Empty 的效能來得好?!
依常理推斷, 第11列的程式碼, 只是將 String.Empty 的內容複製給 str, 而第26列的程式碼, 會不斷產生新的 "" 物件, 再將其位址交給 str; 所以 String.Empty 的效能, 想當然耳, 會比 "" 來得快; 但實測結果, 並非如此. 看來微軟有暗摃什麼撇步在裡面 ...

實測結果追蹤

參考 stack workflow 的一篇文章 What is the difference between String.Empty and “” (empty string)? 有提到, 在 .NET 2.0 之前, "" 會動態建立物件, String.Empty 則不會 ...
"In .Net pre 2.0, "" creates an object while String.Empty creates no object. So it is more efficient to use String.Empty."
所以在 .NET 2.0 之前, String.Empty 的效能是比 "" 來得好 (註: 筆者手邊已無 .NET 1.x 的環境可以測了 ...)

但 .NET 2.0 之後呢? 又找到  Level Up 的 [.Net Concept]理解並善用String pool 這篇文章. 發現其實 .NET 程式在編譯的過程中, 其實, 會有一個 hash table; 其 key 為字串內容, value 為字串位址; 所以, 在上面的處理結果中, 有一個 String.IsInterned(str) 的呼叫, 就是去查這個 hash table 裡, 有沒有這個字串存在, 如果有, 就不用再產生新的字串物件.
注意: 這個在 "編譯" 時期就已經決定了; 當然, "執行" 時期也是可以強迫加入.

所以 ...

string str11 = String.Empty;
string str12 = String.Empty;

採用已建立在 mscorlib.dll 裡的靜態空字串物件, 是比較省記憶體的方式, 即使多個變數, 仍會指向該空間, ex: 0x41 這個位址; 但經實測, 這個也被放到 String Pool 裡了.

string str21 = "";
string str22 = "";

建立一個空字串的物件在記憶體, 然後由一個變數指向它; 但會被 String Pool 優化, 所以基本上與 String.Empty 差異不大

string str31 = "ABC";
string str32 = "ABC";

建立一個 "ABC" 字串的物件在記憶體, 然後由一個變數指向它


上述的說明, 以下圖表示


總結

網路上許多人云亦云的傳聞, 看了文章, 還是不清楚在說啥, 有時還是需要親自驗證一下, 找出藏在背後的細節.

由於 String.Empty 與 "" 實際上沒太大差異, 所以, 就看專案或公司的 "程式撰寫規範" 要採用那一個初始化的方式, 其實對效能都沒有影響. ^^

這篇文章主要因為網路上很多人都認為 String.Empty 比 "" 效能來得好, 但實際上測, 卻又無法證實, 故還是作了一下實證, 而發現背後的一些細節.

說實在的, 實務上, 會對常數字串作很多的 LOOP 作指派的案例, 還真的很少.

比較極端的例子是: 假設有 10,000 次的 LOOP, 在 .NET 1.X
(1) String.Empty: 只會有一個物件
(2) "": 會產生 10,000 個字串物件
這樣, 在記憶體及資源回收, 會有耗損.
所以, 在 .NET 2.0 以後, 針對了 "常數字串" 在編譯時期作優化處理, 避免動態產生及回收字串物件.

對於優化機制有興起的朋友, 可以參考 Level Up 寫的那 2 篇文章, 有針對編譯時期優化的細節作探討.

參考文件





沒有留言:

張貼留言