原文:bit.ly/3wSpO4o
做者:Nikita Starichenko
翻譯:精緻碼農數組
你們好!今天我想和你們分享幾個 .NET 的性能小貼士與基準測試。併發
個人系統環境:性能
我將以百分比的形式提供基準測試結果,其中 100% 是最快的結果。測試
咱們知道,字符串 string
是不可變的。所以,每當你拼接字符串時,就會分配一個新的字符串對象,並填充內容,最終被回收。全部這些都有昂貴開銷,這就是爲何 StringBuilder
在字符串拼接時總有更好的性能。ui
基準測試例子:this
private static StringBuilder sb = new(); [Benchmark] public void Concat3() => ExecuteConcat(3); [Benchmark] public void Concat5() => ExecuteConcat(5); [Benchmark] public void Concat10() => ExecuteConcat(10); [Benchmark] public void Concat100() => ExecuteConcat(100); [Benchmark] public void Concat1000() => ExecuteConcat(1000); [Benchmark] public void Builder3() => ExecuteBuilder(3); [Benchmark] public void Builder5() => ExecuteBuilder(5); [Benchmark] public void Builder10() => ExecuteBuilder(10); [Benchmark] public void Builder100() => ExecuteBuilder(100); [Benchmark] public void Builder1000() => ExecuteBuilder(1000); public void ExecuteConcat(int size) { string s = ""; for (int i = 0; i < size; i++) { s += "a"; } } public void ExecuteBuilder(int size) { sb.Clear(); for (int i = 0; i < size; i++) { sb.Append("a"); } }
結果:線程
1. 3 string concatenations - 218% (35.21 ns) 2. 3 StringBuilder concatenations - 100% (16.09 ns) 1. 5 string concatenations - 277% (66.99 ns) 2. 5 StringBuilder concatenations - 100% (24.16 ns) 1. 10 string concatenations - 379% (160.69 ns) 2. 10 StringBuilder concatenations - 100% (42.37 ns) 1. 100 string concatenations - 711% (2,796.63 ns) 2. 100 StringBuilder concatenations - 100% (393.12 ns) 1. 1000 string concatenations - 3800% (144,100.46 ns) 2. 1000 StringBuilder concatenations - 100% (3,812.22 ns)
.NET 提供了不少集合類型,好比 List<T>
, Dictionary<T>
, 和 HashSet<T>
。全部這些集合都有動態的容量,當你添加更多的項目時,它們的大小會自動擴大。翻譯
當集合達到其大小限制時,它將分配一個新的更大的內存緩衝區,這意味着要進行額外的開銷去分配容量。設計
基準測試例子:指針
[Benchmark] public void ListDynamicCapacity() { List<int> list = new List<int>(); for (int i = 0; i < Size; i++) { list.Add(i); } } [Benchmark] public void ListPlannedCapacity() { List<int> list = new List<int>(Size); for (int i = 0; i < Size; i++) { list.Add(i); } }
在第一個方法中,List
集合使用默認容量初始化,並動態擴大。在第二個方法中,初始容量被設置爲它所須要的固定大小。
對於 1000 個項目,其結果是:
1. List Dynamic Capacity - 140% (2.490 us) 2. List Planned Capacity - 100% (1.774 us)
Dictionary
和 HashSet
的測試結果是:
1. Dictionary Dynamic Capacity - 233% (20.314 us) 2. Dictionary Planned Capacity - 100% (8.702 us) 1. HashSet Dynamic Capacity - 223% (17.004 us) 2. HashSet Planned Capacity - 100% (7.624 us)
數組的分配和回收的開銷多是至關昂貴的,高頻地執行這些分配會增長 GC 的壓力並損害性能。一個優雅的解決方案使用是 System.Buffers.ArrayPool
類,它能夠在 NuGet 的 Systems.Buffers
中找到。
這個思想和 ThreadPool
很類似。爲數組分配一個共享緩衝區,你能夠重複使用,而不須要實際分配和回收它們佔用的內存。基本用法是調用 ArrayPool<T>.Shared.Rent(size)
,這將返回一個常規數組,你能夠以任何方式使用它。完成後,調用 ArrayPool<int>.Shared.Return(array)
將緩衝區返回到共享池中。
基準測試例子:
[Benchmark] public void RegularArray() { int[] array = new int[ArraySize]; } [Benchmark] public void SharedArrayPool() { var pool = ArrayPool<int>.Shared; int[] array = pool.Rent(ArraySize); pool.Return(array); }
ArraySize = 1000
的結果:
1. Regular Array - 2270% (440.41 ns) 2. Shared ArrayPool - 100% (19.40 ns)
當涉及到對象回收時,Struct 有以下幾個好處:
ObjectHeader
和 MethodTable
。基準測試例子:
class VectorClass { public int X { get; set; } public int Y { get; set; } } struct VectorStruct { public int X { get; set; } public int Y { get; set; } } private const int ITEMS = 10000; [Benchmark] public void WithClass() { VectorClass[] vectors = new VectorClass[ITEMS]; for (int i = 0; i < ITEMS; i++) { vectors[i] = new VectorClass(); vectors[i].X = 5; vectors[i].Y = 10; } } [Benchmark] public void WithStruct() { VectorStruct[] vectors = new VectorStruct[ITEMS]; // At this point all the vectors instances are already allocated with default values for (int i = 0; i < ITEMS; i++) { vectors[i].X = 5; vectors[i].Y = 10; } }
結果:
1. With Class - 742% (88.83 us) 2. With Struct - 100% (11.97 us)
在沒有基準測試的狀況下,不要使用 ConcurrentBag<T>
。這個集合是爲很是特殊的使用場景而設計的(當常常有項目被排隊的線程刪除時)。若是須要一個併發的集合隊列,請選擇 ConcurrentQueue<T>
。
基準測試例子:
private static int Size = 1000; [Benchmark] public void Bag() { ConcurrentBag<int> bag = new(); for (int i = 0; i < Size; i++) { bag.Add(i); } } [Benchmark] public void Queue() { ConcurrentQueue<int> bag = new(); for (int i = 0; i < Size; i++) { bag.Enqueue(i); } }
結果:
1. ConcurrentBag - 165% (24.21 us) 2. ConcurrentQueue - 100% (14.64 us)
感謝你們閱讀!