C#中誰最快:結構還是類?
前言
在記憶體當道的日子裡,無論什麼時候都要考慮這些程式碼是否會影響程式效能呢?
在現在的世界裡,幾乎不會去考慮用了幾百毫秒,可是在特別的場景了,往往這幾百毫米確影響了整個專案的快慢。
通過了解這兩者之間的效能差異,希望幫助大家在合適的場景裡選擇正確的編碼。
例項
public class PointClass { public int X { get; set; } public int Y { get; set; } public PointClass(int x, int y) { X = x; Y = y; } } public class PointClassFinalized : PointClass { public PointClassFinalized(int x, int y) : base(x, y) { } ~PointClassFinalized() { // added a finalizer to slow down the GC } } public struct PointStruct { public int X { get; set; } public int Y { get; set; } public PointStruct(int x, int y) { X = x; Y = y; } } public class StructsTest : PerformanceTest { protected override bool MeasureTestA() { // access array elements var list = new PointClassFinalized[Iterations]; for (int i = 0; i < Iterations; i++) { list[i] = new PointClassFinalized(i, i); } return true; } protected override bool MeasureTestB() { // access array elements var list = new PointClass[Iterations]; for (int i = 0; i < Iterations; i++) { list[i] = new PointClass(i, i); } return true; } protected override bool MeasureTestC() { // access array elements var list = new PointStruct[Iterations]; for (int i = 0; i < Iterations; i++) { list[i] = new PointStruct(i, i); } return true; } }
有一個PointClass
和一個 PointStruct
,這兩者用於存放X 和Y 兩個變數,而且還有一個 PointClassFinalized
。
方法 MeasureTestA
建立了100萬個 PointClassFinalized
例項
方法 MeasureTestB
建立了100萬個 PointClass
例項
方法 MeasureTestC
建立了100萬個 PointStruct
例項
您認為哪種方法最快?
MeasureTestB
和 MeasureTestC
這兩個方法的唯一不同在於一個是建立類 一個是建立結構。
MeasureTestC
僅在17毫秒內完成分配並執行,比 MeasureTestB
為什麼會出現這樣的事情,這裡發生了什麼?
不同的在於結構和類如何儲存在記憶體中。
下面是 PointClass
例項 記憶體佈局:
該列表是一個區域性變數,存放在堆疊中。引用堆上的一組 PointClass
例項
PointClass
是一個引用型別,存放在堆上。
該列表僅維護一個數組,指向儲存在堆上 PointClass
例項。
觀察到上圖的黃色箭頭,在堆上引用了很多例項。
陣列是一組相同的物件,MeasureTestB
這個方法是將一組相同的物件存放在陣列中。
當訪問指定陣列元素時,.NET執行時需要檢索物件引用,然後“跟隨”引用以獲取PointClass
例項。
當陣列元素超出範圍時,.NET垃圾收集器就會開始回收PointClass
MeasureTestA
方法中 的PointClassFinalized
類 其實增加了額外時間。
.NET Framework在單個執行緒上執行所有終結器,執行緒必須在垃圾回收器可以回收記憶體之前依次處理1,000,000個物件。
可以看到MeasureTestA
比MeasureTestB
慢1.7倍。
我們來看看 PointStruct
的記憶體佈局:
結構是值型別,所有 PointStruct
例項都儲存在陣列本身中。堆上只有一個物件。
初始化陣列,.NET執行庫可以將X和Y值直接寫入數組裡。無需在堆上建立新物件,也不需要引用它。
當訪問指定陣列元素時,.NET執行時可以直接檢索結構。
當超出範圍時,.NET垃圾回收器只需要處理單個物件。
總結
我們總要使用結構嗎?要分情況看:
- 當您儲存超過30-40個位元組的資料時,請使用類。
- 儲存引用型別時,請使用類。
- 當您儲存多於幾千個例項時,請使用類。
- 如果列表是長的生命週期的,請使用類。
- 在所有其他情況下,使用結構。
相關連結:
- destructors
- classes-and-structs
- medium