普通人如果說什麼事情慢,指的是 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、記憶體、硬碟等)之外,在軟體上也要下功夫進行優化。優化涉及的方面很多,程式碼優化是其中很重要的環節。本文從“黑盒”角度舉了幾個例子並進行實際測試,以揭示不同方法的實現效能。