C# 程式碼效能優化舉例

WildPeak發表於2022-01-20

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

 

相關文章