2017-08-11 114 views
6

在C#中,有結構和類。結構通常(即有例外)分配堆棧並且類總是分配堆。因此,類實例向GC施加壓力,被認爲比結構「慢」。微軟有a best practice guide何時使用類結構。這是說要考慮,如果一個結構:類型應該是盒裝的最佳做法嗎?

  • 它在邏輯上表示單個值,類似於原始類型(int,double等)。
  • 它有一個16字節以下的實例大小。
  • 它是不可變的。
  • 它不會經常被裝箱。

在C#,使用大於16個字節,一般所述結構實例比垃圾收集類實例來執行更差(動態分配)。

何時裝箱實例(堆分配)在速度方面比非盒裝等效實例(堆棧分配)更好?關於何時應該動態分配(在堆上)而不是堅持默認的堆棧分配有什麼最佳做法嗎?

回答

6

TL; DR:從無拳擊開始,然後輪廓。


堆棧分配VS盒裝分配

這也許是更明確的:

  • 堅持到堆棧,
  • 除非值足夠大,它會吹它起來。

雖然語義寫作fn foo() -> Bar意味着從被調用框架調用者幀移動Bar,實際上,你更有可能以fn foo(__result: mut * Bar)簽名相當於是主叫其堆棧分配空間,並傳遞到結束一個指向被調用者的指針。

這可能並不總是足以避免複製,因爲一些模式可能會阻止直接返還口寫着:

fn defeat_copy_elision() -> WithDrop { 
    let one = side_effectful(); 
    if side_effectful_too() { 
     one 
    } else { 
     side_effects_hurt() 
    } 
} 

這裏,也沒有神奇:

  • 如果編譯器使用one的返回槽,那麼在分支評估爲false的情況下,它必須將one移出然後將新的WithDrop實例化到其中,最後銷燬one,
  • 如果編譯器在當前棧上實例化one,並且必須返回它,那麼它必須執行一個副本。

如果類型不需要Drop,那就沒有問題了。

儘管有這些古怪的情況下,我建議堅持堆棧如果可能的話,除非分析顯示它會是有益的箱子的地方。


聯成員或盒裝會員

這種情況要複雜得多:

  • struct/enum的大小會受到影響,因此CPU的緩存行爲的影響:

    • 少FR equently使用大變形是拳擊(或他們的拳擊部分)的候選資格,
    • 不經常訪問的大成員拳擊一個很好的候選人。
  • 的同時,也有拳擊費用:

    • 它與Copy類型不兼容,並含蓄地實現Drop(正如上面看到的,禁用某些優化),
    • 分配/釋放存儲器具有極大等待時間,
    • 訪問盒裝存儲器介紹了數據依賴性:可以不知道哪個高速緩存線知道地址之前請求。

結果,這是一個非常精細的平衡。對成員進行裝箱或拆箱可能會提高代碼庫某些部分的性能,同時降低其他代碼的性能。

肯定是有沒有一個放之四海而皆準。

因此,再一次,我建議避免拳擊,直到分析揭示了它會是有益的箱子的地方。

考慮在Linux上,任何內存分配針對其存在的過程中沒有多餘的內存可能需要一個系統調用,如果在操作系統沒有備用內存可能觸發OOM殺手殺死進程,在此時它的內存被搶救並可用。簡單的malloc(1)可能很容易要求毫秒