iT邦幫忙

0

筆記:.NET List 物件的記憶體配置與效能

  • 分享至 

  • xImage
  •  

筆記:.NET List 物件的記憶體配置與效能

List<T> 在 .net 的程式裡應該可算是隨處可見。它將 Array 包裝起來,讓我們在使用時可以達到動態增加元素的效果。

然而,這種便利性嚴格來說是有代價的。如果你使用的情境,是會不斷的大量新增元素,並且也沒有在初始時就給定 capacity 的話,List<T> 的動態 extend 可能會使用過多的記憶體與 GC 更大的負擔。


所謂動態 extend 的實作

通常都是初始的時候沒有給 capacity 才會在之後 Add 新元素時需要做 extend 的動作。

在 new list 時沒有給 capacity,預設會有 0 的容量。然後當程式第一次 Add 的時候,就會馬上去從 heap allocate 4 個 list 元素的位置出來(如果是 reference type 的話,只是存放指標位置的記憶體空間)。

再接下來的 Add 都會去判斷元素的容量夠不夠。當他不夠的時候,就會以現在的 容量 乘以 2,再去 heap 找一塊可以用的空間重新 allocate。

而細節就是這段 allocate 的動作。因為每次的重新 allocate,它除了要重新 allocate 空間,還要再做一次的動作,把舊資料搬到這塊新的記憶體空間去。最後再把舊的記憶體空間直接捨棄,等著被 GC。

從這個順序來看,每一次所謂動態 extend,都有三件很浪費資源的事情。

  • 重新在 heap 分配記憶體
  • 把舊資料搬移
  • 舊資料等著被 GC

List 優點

除了程式比較好寫跟閱讀性較好之外,暫時想不到什麼其他優點……

List 缺點

就是剛才提到的會較浪費資源,尤其是當你元素越多或是頻繁的 new list 物件來使用時,這些問題就會慢慢的浮現出來。


Solution

在使用時,儘量先給定 capacity 大小(如果可以的話)。但這也是要 by 情況來看。如果要處理的資料偏少量,或是資料筆數難以事先判定的話,這個想法也是有困難的。


個人經驗

用 List 來接大量物件算是一把雙面刃。因為在 happy flow 的使用情境下,讓它自己動態處理記憶的部份,其實都不會有問題。

可是當某一支程式效能就是要計較到記憶體與 GC 使用時,也許可考慮換個方向來思考。例如,如果要處理的資料是經過某段邏輯處理就不再需要,那可能也不需要一次把那麼大的資料都放到記憶體裡。

這時候如果改用 stream 或是 RingBuffer 的資料結構來解,可能會會更漂亮一些。


小小筆記

之前在練習 golang 時候,有讀到像 Slice 這種集合物件在 extend 的時候,做法也是類似。

像是 goloan 在元素只有在某個數值前(AI 說是 256)是直接 allocate 2 倍空間;但當元素超過 256 個後,每次的 extend 就會從 2 倍改回 1.5 倍(新版好像又調成 1.25 倍了)。

簡單來說,這種所謂的動態其實背後一定有它的代價。所謂的方便使用確實是適合一版的 happly flow 開發。但當你今天是要讓程式跑到一個極致時,這些細節可能都要自己操控好會比較好。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言