普通人如果說什么事情慢,指的是 5 分鍾,10 分鍾,或者 1 個小時、2 個小時。而程序員要說什么事情慢,他們指的是 2 納秒。
每個納秒對程序員來說都是非常寶貴的,所以,要對代碼進行優化,優化,再優化,每個納秒都不要浪費。
在 C# 程序中,完成一件任務通常都有若干種方法,但這些方法之間是存在一些差異的,特別是性能上的差異。本文嘗試着舉幾個例子來說明這種差異。
1. 裝箱還是不裝箱(to box or not to box)
一般來說,值類型的數據都是在棧上操作的,而引用類型的數據都是在堆上的,而當值類型需要作為引用類型操作時,都要先對值類型數據進行裝箱,使其變為引用類型。但裝箱操作是一個成本很高的操作,要盡量避免使用。如對於如下代碼:
static string GetArrayInfo(Array a) { string s = $"An array with {a.Length} elements."; return s; }
其中,a.Length 是一個整型的值類型變量,與字符串混合在一起時,需要進行裝箱操作。可改為如下代碼以避免裝箱操作:
static string GetArrayInfo(Array a) { string s = "An array with " + a.Length.ToString() + " elements."; return s; }
2. 構造字符串(building strings)
構造字符串,可以使用 StringBuilder,也可以直接用 + 操作符(編譯為對 Concat() 方法的調用)連接字符串。一般來說,使用 StringBuilder 是一種高性能的字符串構造方案,但也不盡然,尤其是 StringBuilder 的 AppendFormat() 方法,成本很高,應盡量避免使用。舉例如下。
static string Greeting(string name) { StringBuilder sb = new(); _ = sb.AppendFormat("Hello, {0}", name ?? "null"); return sb.ToString(); }
下面的代碼使用 Append() 方法構造字符串:
static string Greeting(string name) { StringBuilder sb = new(); _ = sb.Append("Hello, "); _ = sb.Append(name ?? "null"); return sb.ToString(); }
最后,我們用 + 操作符(Concat() 方法)構造同樣的字符串:
static string Greeting(string name) { return "Hello, " + name ?? "null"; }
經過實測,+ 操作符方法(Concat() 方法)性能最好,StringBuilder 的 Append() 次之,AppendFormat() 最差。
3. 類型轉換(type conversion)
如將一個數字字符串轉換為整型數,一般有三種方法實現轉換:
// 方法1: _ = int.TryParse("123", out int n); // 方法2: int n = int.Parse("123"); // 方法3: int n = Convert.ToInt32("123");
這三種方法都可以。從安全性上講,int.TryParse() 最安全,從性能上說,則 Convert.ToInt32() 最好(但也相差不大)。如果能確定輸入字符串的合法性,則盡量使用 Convert.ToInt32() 方法,反之,則使用 int.TryParse() 以避免拋出異常。
以上三個實例的實測數據如下圖所示,這是經過 100 次循環,每次循環執行 1000000 次調用,經過平均后得出的結果。
提高程序的運行性能,除了選用高性能的硬件(高性能CPU、內存、硬盤等)之外,在軟件上也要下功夫進行優化。優化涉及的方面很多,代碼優化是其中很重要的環節。本文從“黑盒”角度舉了幾個例子並進行實際測試,以揭示不同方法的實現性能。