前言
在記憶體當道的日子裡,無論什麼時候都要考慮這些程式碼是否會影響程式效能呢?
在現在的世界裡,幾乎不會去考慮用了幾百毫秒,可是在特別的場景了,往往這幾百毫米確影響了整個專案的快慢。
通過了解這兩者之間的效能差異,希望幫助大家在合適的場景裡選擇正確的編碼。
例項
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
方法快8.6倍!
為什麼會出現這樣的事情,這裡發生了什麼?
不同的在於結構和類如何儲存在記憶體中。
下面是 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個位元組的資料時,請使用類。
- 儲存引用型別時,請使用類。
- 當您儲存多於幾千個例項時,請使用類。
- 如果列表是長的生命週期的,請使用類。
- 在所有其他情況下,使用結構。