技術(shù)頻道導(dǎo)航
HTML/CSS
.NET技術(shù)
IIS技術(shù)
PHP技術(shù)
Js/JQuery
Photoshop
Fireworks
服務(wù)器技術(shù)
操作系統(tǒng)
網(wǎng)站運(yùn)營

贊助商

分類目錄

贊助商

最新文章

搜索

[C#技巧]為什么List<struct>比List<class>快15倍

作者:admin    時(shí)間:2023-5-6 17:30:29    瀏覽:

測試發(fā)現(xiàn),C#中 List<Struct> 的分配速度比 List<Class> 快 15 倍,這是一個(gè)令人驚詫的結(jié)果,但我想知道為什么會(huì)這樣!

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 });
}

ListOfStructsTestListOfClassesTest方法幾乎相同。第一個(gè)方法分配一百萬個(gè)PointClass實(shí)例并將它們添加到列表中,而第二個(gè)方法分配一百萬個(gè)PointStruct實(shí)例并將它們也添加到列表中。類型PointClassPointStruct具有相同的成員,但唯一的小而關(guān)鍵的區(qū)別是,PointClass是一個(gè)類,而是PointStruct一個(gè)結(jié)構(gòu)。

測試結(jié)果非常令人印象深刻:

 

ListOfStructsTest方法比ListOfClassesTest方法快 15 倍以上。

下面我們試著分析一下為什么會(huì)出現(xiàn)如此巨大的時(shí)差。 

在堆上分配引用類型實(shí)例和結(jié)構(gòu)實(shí)例的區(qū)別

對我們來說,第一件事是理解在堆上分配一個(gè)引用類型實(shí)例和在堆棧上分配一個(gè)結(jié)構(gòu)實(shí)例之間的區(qū)別。

public void Test() 

   var obj = new object(); //引用類型分配
   
   int x = 12; //值類型分配
}

在托管堆中為引用類型分配內(nèi)存的時(shí)間通常是快速操作,對象被連續(xù)分配和存儲(chǔ)。公共語言運(yùn)行時(shí)具有指向內(nèi)存中第一個(gè)空閑空間的指針,分配新對象涉及將新對象的大小添加到指針。

將對象放在托管堆上后,其地址將寫回在堆棧上創(chuàng)建的引用obj。

總的來說整個(gè)過程還是挺便宜的,然而,為引用類型的對象分配內(nèi)存的過程并不總是那么容易,并且可能涉及額外的繁重部分。

如果引用類型大于 85K 字節(jié),運(yùn)行時(shí)將花費(fèi)更多時(shí)間在大對象堆中尋找合適的位置來存儲(chǔ)對象,因?yàn)槟抢锏膬?nèi)存是碎片化的(空閑塊或地址空間中的“空洞”)。

在小對象堆中沒有更多可用空間來存儲(chǔ)應(yīng)用程序請求的對象的情況下,引用類型對象分配很慢。當(dāng)發(fā)生這種情況時(shí),公共語言運(yùn)行時(shí)需要運(yùn)行垃圾收集過程。如果垃圾收集器沒有釋放足夠的內(nèi)存,運(yùn)行時(shí)會(huì)請求額外的虛擬內(nèi)存頁面。

如何在堆棧上分配一個(gè)值類型實(shí)例?

為值類型分配內(nèi)存幾乎是即時(shí)操作,分配時(shí)間幾乎不依賴于值類型的大小。運(yùn)行時(shí)唯一應(yīng)該做的就是創(chuàng)建一個(gè)適當(dāng)大小的堆棧幀來存儲(chǔ)值類型和修改堆棧指針。

要點(diǎn)是,將值類型的實(shí)例放在堆棧上是快速的,更重要的是,與在堆上分配引用類型對象相比,它在時(shí)間上是一個(gè)確定性的過程。

實(shí)例分析

現(xiàn)在讓我們回到我們的例子。

當(dāng)一個(gè)引用類型的一百萬個(gè)實(shí)例被分配時(shí),它們被一個(gè)一個(gè)地推入托管堆,并且引用被存儲(chǔ)回集合實(shí)例。實(shí)際上,會(huì)有100萬+1個(gè)對象進(jìn)入內(nèi)存。

 

然而,當(dāng)一個(gè)值類型的一百萬個(gè)實(shí)例被分配時(shí),只有一個(gè)對象被推入托管堆,它是一個(gè)集合的實(shí)例。一百萬個(gè)結(jié)構(gòu)將嵌入到List<T>實(shí)例中。創(chuàng)建實(shí)例后,運(yùn)行時(shí)唯一要做的就是用數(shù)據(jù)填充List<T>

 

在為大型集合選擇結(jié)構(gòu)而不是類時(shí),開發(fā)人員不僅受益于快速分配時(shí)間,還受益于發(fā)布時(shí)間。

如果開發(fā)人員分配了一百萬個(gè)PointClass實(shí)例,在“標(biāo)記和清理”階段,垃圾收集器將不得不掃描一百萬個(gè)對象并檢查每個(gè)對象是否還有引用。然后,在“壓縮”階段,垃圾收集器將不得不移動(dòng)一百萬個(gè)對象。最終,存儲(chǔ)在List<PointClass>實(shí)例中的地址應(yīng)該更新為新地址。這是很多工作。

但對于垃圾收集器而言,當(dāng)開發(fā)人員分配一百萬個(gè)List<PointStruct> 實(shí)例時(shí)情況會(huì)好得多,因?yàn)槔占鞅仨毷褂猛泄芏阎械奈ㄒ粚?shí)例PointStruct。

總結(jié)

結(jié)構(gòu)(struct)可以比類(class)更高效,但在用struct或其他方式替換class關(guān)鍵字之前,請始終仔細(xì)分析你的具體情況。程序員的工作不是盲目地遵循建議或最佳實(shí)踐,而是選擇最合適的工具、方法、方式來以最佳方式解決各自的獨(dú)特案例。

相關(guān)文章

標(biāo)簽: asp.net  CSharp  List方法  struct  class  代碼性能  優(yōu)化  
x
  • 站長推薦
/* 左側(cè)顯示文章內(nèi)容目錄 */