C#中誰最快:結構還是類?

androllen發表於2019-07-29

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 例項

您認為哪種方法最快?

C#中誰最快:結構還是類?

MeasureTestBMeasureTestC 這兩個方法的唯一不同在於一個是建立類 一個是建立結構。

MeasureTestC 僅在17毫秒內完成分配並執行,比 MeasureTestB 方法快8.6倍!

為什麼會出現這樣的事情,這裡發生了什麼?

不同的在於結構和類如何儲存在記憶體中。

下面是 PointClass 例項 記憶體佈局:

C#中誰最快:結構還是類?

該列表是一個區域性變數,存放在堆疊中。引用堆上的一組 PointClass例項

PointClass 是一個引用型別,存放在堆上。

該列表僅維護一個陣列,指向儲存在堆上 PointClass 例項。

觀察到上圖的黃色箭頭,在堆上引用了很多例項。

陣列是一組相同的物件,MeasureTestB 這個方法是將一組相同的物件存放在陣列中。

當訪問指定陣列元素時,.NET執行時需要檢索物件引用,然後“跟隨”引用以獲取PointClass例項。

當陣列元素超出範圍時,.NET垃圾收集器就會開始回收PointClass物件記憶體,在 MeasureTestA 方法中 的PointClassFinalized類 其實增加了額外時間。

.NET Framework在單個執行緒上執行所有終結器,執行緒必須在垃圾回收器可以回收記憶體之前依次處理1,000,000個物件。

可以看到MeasureTestAMeasureTestB慢1.7倍。

我們來看看 PointStruct 的記憶體佈局:

C#中誰最快:結構還是類?

結構是值型別,所有 PointStruct 例項都儲存在陣列本身中。堆上只有一個物件。

初始化陣列,.NET執行庫可以將X和Y值直接寫入陣列裡。無需在堆上建立新物件,也不需要引用它。

當訪問指定陣列元素時,.NET執行時可以直接檢索結構。

當超出範圍時,.NET垃圾回收器只需要處理單個物件。

總結

我們總要使用結構嗎?要分情況看:

  • 當您儲存超過30-40個位元組的資料時,請使用類。
  • 儲存引用型別時,請使用類。
  • 當您儲存多於幾千個例項時,請使用類。
  • 如果列表是長的生命週期的,請使用類。
  • 在所有其他情況下,使用結構。

相關連結:

相關文章