slice 擴容後容量及內存如何計算

1. 擴容後預估容量

假設現在有一個長度爲 2 的切片,對其進行擴容,增加三個元素

sli := []int{1,2}
sli = append(sli, 3, 4, 5
 

對於擴容後的切片,長度爲 5,這一點沒有任何爭議。

但容量呢?難道也是 5?

經過運行驗證,實際的容量爲 6 。

什麼情況?這 6 是如何計算出來的呢?

這就不得不去 Go 源代碼中一探究竟,在 runtime/slice.go 關於 slice 擴容增長的代碼如下:

newcap := old.cap
if newcap+newcap < cap {
    newcap = cap
} else {
    for {
        if old.len < 1024 {
            newcap += newcap
        } else {
            newcap += newcap / 4
        }
        if newcap >= cap {
            break
        }
    }
}
 

對於這段代碼,只要理解前兩行,其他的就不攻自破了

  • 第一行的 old.cap:擴容前的容量,對於此例,就是 2
  • 第二行的 cap:擴容前容量加上擴容的元素數量,對於此例,就是 2+3

整段代碼的核心就是要計算出擴容後的預估容量,也就是 newcap,計算的具體邏輯是:

  1. 若 old.cap * 2 小於 cap,那 newcap 就取大的 cap
  2. 若 old.cap * 2 大於 cap,並且old.cap 小於 1024,那 newcap 還是取大,也即 newcap = old.cap * 2
  3. 若 old.cap * 2 大於 cap,但是old.cap 大於 1024,那兩倍冗餘可能就有點大了,係數換成 1.25,也即 newcap = old.cap * 2

但 newcap 只是預估容量,並不是最終的容量,要計算最終的容量,還需要參考另一個維度,也就是內存分配。

2. 內存的分配規律

舉個現實中的例子來說

你家裏有五個人,每個人都想喫綠豆糕,因此你的需求就是 5,對應上例中的 cap ,於是你就到超市裏去買。

但超市並不是你家開的,綠豆糕都是整盒整盒的賣,沒有賣散的,每盒的數量是 6 個,因此你最少買 6 個。

每次購買的最少數量,就可以類比做 Go 的內存分配規律。

只有瞭解了 Go 的內存分配規律,我們才能準確的計算出我們最少得買多少的綠豆糕(得申請多少的內存,分配多少的容量)。

關於內存管理模塊的代碼,在 runtime/sizeclasses.go

// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         32        8192      256           0     46.88%
//     4         48        8192      170          32     31.52%
...
//    17        256        8192       32           0      5.86%
//    18        288        8192       28         128     12.16%
//    19        320        8192       25         192     11.80%
//    20        352        8192       23          96      9.88%
//    21        384        8192       21         128      9.51%
//    22        416        8192       19         288     10.71%
//    23        448        8192       18         128      8.37%
//    24        480        8192       17          32      6.82%
//    25        512        8192       16           0      6.05%
...
//    66      32768       32768        1           0     12.50%
Go
Copy

從上面這個表格中,可以總結出一些規律。

  • 在小於16字節時,每次以8個字節增加
  • 當大於16小於2^8時,每次以16字節增加
  • 當大於2^8小於2^9時以32字節增加
  • 依此規律…

3. 匹配到合適的內存

第一節中我們例子中,主人公是一個元素類型爲 int 的切片,每個 int 佔用爲 8 個字節,由於我們計算出的 newcap 爲 5,因此新的切片,最少最少要佔用 5*8 = 40 個字節。

再到第二節中的表格中查看,發現離 40 byte 最接近的是 32 和 48 兩個檔位。

如果是 32 byte,就是不夠用了,

因此 只能選擇 48 這個檔位去分配內存。

有了實際分配的內存,再反回去計算容量,就是擴容後真實的切片容量,也就是 48/8 = 6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章