|
|
|
|
|
測試發(fā)現(xiàn),C#中 List<Struct>
的分配速度比 List<Class>
快 15 倍,這是一個令人驚詫的結(jié)果,但我想知道為什么會這樣!
struct比class快15倍
下面是測試代碼:
public class PointClass
{
public int X { get; set; }
public int Y { get; set; }
}
public struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
}
[Benchmark]
public void ListOfClassesTest()
{
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 ListOfStructsTest()
{
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 });
}
ListOfStructsTest
和ListOfClassesTest
方法幾乎相同。第一個方法分配一百萬個PointClass
實例并將它們添加到列表中,而第二個方法分配一百萬個PointStruct
實例并將它們也添加到列表中。類型PointClass
和PointStruct
具有相同的成員,但唯一的小而關(guān)鍵的區(qū)別是,PointClass
是一個類,而是PointStruct
一個結(jié)構(gòu)。
測試結(jié)果非常令人印象深刻:
ListOfStructsTest
方法比ListOfClassesTest
方法快 15 倍以上。
下面我們試著分析一下為什么會出現(xiàn)如此巨大的時差。
在堆上分配引用類型實例和結(jié)構(gòu)實例的區(qū)別
對我們來說,第一件事是理解在堆上分配一個引用類型實例和在堆棧上分配一個結(jié)構(gòu)實例之間的區(qū)別。
public void Test()
{
var obj = new object(); //引用類型分配
int x = 12; //值類型分配
}
在托管堆中為引用類型分配內(nèi)存的時間通常是快速操作,對象被連續(xù)分配和存儲。公共語言運行時具有指向內(nèi)存中第一個空閑空間的指針,分配新對象涉及將新對象的大小添加到指針。
將對象放在托管堆上后,其地址將寫回在堆棧上創(chuàng)建的引用obj
。
總的來說整個過程還是挺便宜的,然而,為引用類型的對象分配內(nèi)存的過程并不總是那么容易,并且可能涉及額外的繁重部分。
如果引用類型大于 85K 字節(jié),運行時將花費更多時間在大對象堆中尋找合適的位置來存儲對象,因為那里的內(nèi)存是碎片化的(空閑塊或地址空間中的“空洞”)。
在小對象堆中沒有更多可用空間來存儲應(yīng)用程序請求的對象的情況下,引用類型對象分配很慢。當(dāng)發(fā)生這種情況時,公共語言運行時需要運行垃圾收集過程。如果垃圾收集器沒有釋放足夠的內(nèi)存,運行時會請求額外的虛擬內(nèi)存頁面。
如何在堆棧上分配一個值類型實例?
為值類型分配內(nèi)存幾乎是即時操作,分配時間幾乎不依賴于值類型的大小。運行時唯一應(yīng)該做的就是創(chuàng)建一個適當(dāng)大小的堆棧幀來存儲值類型和修改堆棧指針。
要點是,將值類型的實例放在堆棧上是快速的,更重要的是,與在堆上分配引用類型對象相比,它在時間上是一個確定性的過程。
實例分析
現(xiàn)在讓我們回到我們的例子。
當(dāng)一個引用類型的一百萬個實例被分配時,它們被一個一個地推入托管堆,并且引用被存儲回集合實例。實際上,會有100萬+1個對象進入內(nèi)存。
然而,當(dāng)一個值類型的一百萬個實例被分配時,只有一個對象被推入托管堆,它是一個集合的實例。一百萬個結(jié)構(gòu)將嵌入到List<T>
實例中。創(chuàng)建實例后,運行時唯一要做的就是用數(shù)據(jù)填充List<T>
。
在為大型集合選擇結(jié)構(gòu)而不是類時,開發(fā)人員不僅受益于快速分配時間,還受益于發(fā)布時間。
如果開發(fā)人員分配了一百萬個PointClass
實例,在“標(biāo)記和清理”階段,垃圾收集器將不得不掃描一百萬個對象并檢查每個對象是否還有引用。然后,在“壓縮”階段,垃圾收集器將不得不移動一百萬個對象。最終,存儲在List<PointClass>
實例中的地址應(yīng)該更新為新地址。這是很多工作。
但對于垃圾收集器而言,當(dāng)開發(fā)人員分配一百萬個List<PointStruct>
實例時情況會好得多,因為垃圾收集器必須使用托管堆中的唯一實例PointStruct
。
總結(jié)
結(jié)構(gòu)(struct)可以比類(class)更高效,但在用struct
或其他方式替換class
關(guān)鍵字之前,請始終仔細(xì)分析你的具體情況。程序員的工作不是盲目地遵循建議或最佳實踐,而是選擇最合適的工具、方法、方式來以最佳方式解決各自的獨特案例。
相關(guān)文章