|
|
|
|
|
|
為什么要關注代碼性能?因為代碼性能會影響程序的執行效率,在客戶端就表現為等待時間的長短,這就影響到用戶的使用體驗,因此,我們開發人員不得不對代碼性能給予足夠的重視。本文中,我將總結11個提高C#代碼性能的技巧。
1、循環并行化
foreach 是當今最常用的循環,僅次于 for 循環。它提供了靈活性,不用擔心循環計數,它會運行到集合的長度。
在foreach循環中,迭代在同一線程中一個接一個地執行,因此總執行時間將隨集合大小線性增長。
使用 .Net Framework 4.0 提供給開發者的并行版本的foreach循環,性能可以有很大的提升。
Parallel.ForEach 可用于實現 IEnumerable<T> 接口的任何集合,就像常規的 foreach 循環一樣。Parallel.ForEach 將代替開發人員做很多工作:它根據機器上可用的核心將集合分成塊,在單獨的線程中調度和執行塊,然后組合結果。
以下是 foreach 循環的并行版本與普通版本之間的性能比較:

如果集合很小并且單次迭代的執行時間很快,則將 foreach 切換為 Parallel。Parallel.ForEach 可能使性能變差,因為它通過拆分和收集結果增加了管理循環的成本。
2、使用 StringBuilder 而不是字符串連接
在 C# 中,字符串連接在每次調用時都會創建一個新的字符串對象。如果你連接大量字符串,這可能效率低下。為避免這種情況,請改用 StringBuilder。
例子:
var sb = new StringBuilder();
sb.Append("Hello ");
sb.Append("World");
var result = sb.ToString();
3、使用 LINQ 進行過濾和排序
在 C# 中,LINQ 提供了用于篩選、排序和操作集合的強大工具。LINQ 比手動遍歷集合并對每個項目執行操作更有效。
例子:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = numbers.Where(n => n % 2 == 0).OrderByDescending(n => n);4、一次性物化 LINQ 查詢
何為一次性物化 LINQ 查詢?即是不要使用表達式樹來保存結果,而是盡可能通過執行來提取結果。
使用 IEnumerable 或 IQueryable 接口編寫 LINQ 查詢時,開發人員可以具體化(調用 ToList 或 ToArray 類似方法)或不立即具體化查詢(延遲加載)。
如果查詢不是通過調用 ToList/ToArray 方法實現的,多次迭代集合將影響應用程序的性能。
這是一個示例,我們在其中測試延遲加載 LINQ 查詢與物化查詢的性能。
[MemoryDiagnoser]
public class LinqTest {
[Benchmark]
public void NoMaterializedTest() {
var list = Enumerable.Range(0, 50000000);
var filteredlist = list.Where(element => element % 100000 == 0);
foreach(var element in filteredlist) {
//some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
}
[Benchmark]
public void MaterializedTest() {
var list = Enumerable.Range(0, 5000000);
var filteredlist = list.Where(element => element % 10000 == 0).ToList();
//ToList() method will execute the expression tree and get the result for future purpose.
foreach(var element in filteredlist) {
// some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
}
public static void Main() {
//Execute LinqTest class methods to check the benchmark
var result = BenchmarkRunner.Run < LinqTest > ();
}

在這里可以看到差異,與延遲加載 LINQ 查詢相比,物化查詢的性能提高了三倍。盡管這種方法并不總是有效,因為我們可能不會多次執行相同的結果集。在具體化 LINQ 查詢之前始終檢查大小寫。
5、異步編程
異步編程是一種允許你的代碼在等待長時間運行的操作完成時繼續執行的技術。這在處理 I/O 操作時特別有用,例如讀取和寫入文件或發出 Web 請求。
在 C# 中,異步編程是使用 async 和 await 關鍵字實現的。
例子:
async Task<string> ReadFileAsync(string filePath)
{
using (var reader = new StreamReader(filePath))
{
return await reader.ReadToEndAsync();
}
}
6、對大型集合使用 IEnumerable 而不是列表
在 C# 中,列表是存儲和操作對象集合的有效方式。但是,將 IEnumerable 用于廣泛的集合會更有效,因為它避免了創建和維護 List 對象的開銷。
例子:
public IEnumerable<int> GetNumbers()
{
for (int i = 0; i < 1000000; i++)
{
yield return i;
}
}
foreach (var number in GetNumbers())
{
Console.WriteLine(number);
}
7、使用枚舉標志進行位運算
在 C# 中,枚舉可以用作標志,這使您可以對它們執行按位運算。這在將多個值存儲在單個變量中時非常有用。
例子:
[Flags]
public enum Colors
{
None = 0,
Red = 1,
Green = 2,
Blue = 4
}
var colors = Colors.Red | Colors.Green;
8、對小型數據結構使用結構而不是類
在 C# 中,結構是值類型,類是引用類型。結構分配在棧上,而類分配在堆上。對于小型數據結構,使用結構可以更有效,因為它避免了堆分配和垃圾收集的開銷。
例子:
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
var p = new Point(10, 20);
9、使用泛型
泛型是 C# 中的一項強大功能,可讓你創建可重用的代碼。通過使用泛型類型參數定義類或方法,你可以創建類型安全、可重用的組件,該組件可以處理各種數據類型。這可以通過減少你需要編寫的重復代碼來幫助提高性能。
下面是使用泛型創建可重用排序方法的示例:
public static void Sort<T>(T[] array) where T : IComparable<T>
{
for (int i = 0; i < array.Length; i++)
{
for (int j = i + 1; j < array.Length; j++)
{
if (array[j].CompareTo(array[i]) < 0)
{
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
10、使用接口實現松散耦合
在C#中,接口為組件之間的松散耦合提供了一種強大的機制。通過定義服務或功能的接口,您可以確保組件可以輕松換出或更換,而不會影響系統的其余部分。這種方法可以通過最小化更改對整個系統的影響來提高代碼性能。
例如,考慮以下定義用于訪問數據的通用存儲庫的接口:
interface IRepository<T> {
void Save(T entity);
T Get(int id);
}這個接口可以定義一個通用的數據訪問層,可以很容易地替換或更新而不影響系統的其余部分。
11、盡可能使用 Struct 而不是 Class
大多數時候我看到開發人員創建類時沒有對 Class 和 Struct 進行很好的比較。開發人員通常可能需要分配一個數組或 List<T> 在內存中存儲數萬個對象。這個任務可以使用類或結構來解決。
正如我們所看到的, ListOfClassObjectTest 和ListOfStructsObjectTest之間的唯一區別,是第一個測試創建類的實例,而第二個測試創建結構的實例。
如下示例中,PointClass 和 PointStruct 的代碼是相同的:
//Reference type. Stored on Heap.
public class PointClass {
public int X {
get;
set;
}
public int Y {
get;
set;
}
}
//Value type. Stored on Stack
public struct PointStruct {
public int X {
get;
set;
}
public int Y {
get;
set;
}
}
[MemoryDiagnoser]
public class ClassVsStruct {
[Benchmark]
public void ListOfClassObjectsTest() {
const int length = 1000000;
var items = new List < PointClass > (length);
for (int i = 0; i < length; i++) {
items.Add(new PointClass() {
X = i, Y = i
});
}
}
[Benchmark]
public void ListOfStructsObjectTest() {
const int length = 1000000;
var items = new List < PointStruct > (length);
for (int i = 0; i < length; i++) {
items.Add(new PointStruct() {
X = i, Y = i
});
}
}
}
可以看到,使用結構的代碼比使用類的代碼運行速度快 15 倍。
對于類,CLR 必須將一百萬個對象分配給托管堆并將它們的引用存儲回List<T>集合。在結構的情況下,將有唯一的對象分配到托管堆中 ,它是List<T>集合的實例。一百萬個結構將嵌入到該單個集合實例中。
總結
以上11點希望對各位開發者提升代碼質量有所幫助,通過這些開發技巧,開發人員可以創建執行速度更快、占用系統資源更少的應用程序。
