1. 程式人生 > >C#中誰最快:結構還是類?

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

您認為哪種方法最快?

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

MeasureTestC 僅在17毫秒內完成分配並執行,比 MeasureTestB

方法快8.6倍!

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

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

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

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

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

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

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

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

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

當陣列元素超出範圍時,.NET垃圾收集器就會開始回收PointClass

物件記憶體,在 MeasureTestA 方法中 的PointClassFinalized類 其實增加了額外時間。

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

可以看到MeasureTestAMeasureTestB慢1.7倍。

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

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

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

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

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

總結

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

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

相關連結:

  • destructors
  • classes-and-structs
  • medium