最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言切片擴容原理和過程

 更新時間:2025年02月17日 09:42:20   作者:飛川001  
切片(Slice)在 Go 語言中,有一個很常用的數(shù)據(jù)結構,切片是一個擁有相同類型元素的可變長度的序列,它是基于數(shù)組類型做的一層封裝,它非常靈活,支持自動擴容,并發(fā)不安全,本文給大家介紹了Go 語言切片如何擴容,需要的朋友可以參考下

一、結構介紹

切片(Slice)在 Go 語言中,有一個很常用的數(shù)據(jù)結構,切片是一個擁有相同類型元素的可變長度的序列,它是基于數(shù)組類型做的一層封裝。它非常靈活,支持自動擴容。并發(fā)不安全。

切片是一種引用類型,它有三個屬性:指針,長度和容量。

底層源碼定義:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

1.指針: 指向 slice 可以訪問到的第一個元素。

2.長度: slice 中元素個數(shù)。

3.容量: slice 起始元素到底層數(shù)組最后一個元素間的元素個數(shù)。

比如使用 make([]byte, 5) 創(chuàng)建一個切片,它看起來是這樣的:

二、擴容時機與過程

Go 中切片的擴容機制是基于動態(tài)數(shù)組的,這意味著切片的底層數(shù)組會動態(tài)調整大小以適應元素的增加。下面是 Go 切片擴容的一般過程:

1.初始分配:

當使用 make 創(chuàng)建一個切片時,Go 會為其分配一塊初始的底層數(shù)組,并將切片的長度和容量都設置為相同的值。

2.追加元素:

當你使用 append 向切片追加元素時,Go 會檢查是否有足夠的容量來容納新的元素。如果有足夠的容量,新元素會被添加到底層數(shù)組的末尾,切片的長度會增加。如果沒有足夠的容量,就需要進行擴容。

3.擴容:

當切片需要擴容時,Go 會創(chuàng)建一個新的更大的底層數(shù)組(具體的擴容策略看下面擴容原理)。然后,原數(shù)組的元素會被復制到新數(shù)組中,新元素會被添加到新數(shù)組的末尾。最后,切片的引用會指向新的底層數(shù)組,原數(shù)組會被垃圾回收。
這個擴容的過程保證了在大多數(shù)情況下,append 操作都是高效的。由于每次擴容都會涉及元素的復制,因此在涉及大量元素的情況下可能會導致一些性能開銷。如果你知道切片需要存儲的元素數(shù)量,可以使用 make 函數(shù)make([]T, length, capacity)的第三個參數(shù)顯式指定容量,以減少擴容的次數(shù)。

三、擴容原理

Go1.18之前切片的擴容是以容量1024為臨界點,當舊容量 < 1024個元素,擴容變成2倍;當舊容量 > 1024個元素,那么會進入一個循環(huán),每次增加25%直到大于期望容量。
然而這個擴容機制已經被Go 1.18棄用了,官方說新的擴容機制能更平滑地過渡。
具體擴容原理分別如下:

Go 1.18版本 之前擴容原理

在分配內存空間之前需要先確定新的切片容量,運行時根據(jù)切片的當前容量選擇不同的策略進行擴容:

1. 如果期望容量大于當前容量的兩倍就會使用期望容量;

2. 如果當前切片的長度小于 1024 就會將容量翻倍;

3. 如果當前切片的長度大于等于 1024 就會每次增加 25% 的容量,直到新容量大于期望容量;

注:解釋一下第一條:
比如 nums := []int{1, 2} nums = append(nums, 2, 3, 4),這樣期望容量為2+3 = 5,而5 > 2*2,故使用期望容量(這只是不考慮內存對齊的情況下)

記錄容量變化如下:

[0 ->   -1] cap = 0     |  after append 0     cap = 1   
[0 ->    0] cap = 1     |  after append 1     cap = 2   
[0 ->    1] cap = 2     |  after append 2     cap = 4   
[0 ->    3] cap = 4     |  after append 4     cap = 8   
[0 ->    7] cap = 8     |  after append 8     cap = 16  
[0 ->   15] cap = 16    |  after append 16    cap = 32  
[0 ->   31] cap = 32    |  after append 32    cap = 64  
[0 ->   63] cap = 64    |  after append 64    cap = 128 
[0 ->  127] cap = 128   |  after append 128   cap = 256 
[0 ->  255] cap = 256   |  after append 256   cap = 512 
[0 ->  511] cap = 512   |  after append 512   cap = 1024
[0 -> 1023] cap = 1024  |  after append 1024  cap = 1280
[0 -> 1279] cap = 1280  |  after append 1280  cap = 1696
[0 -> 1695] cap = 1696  |  after append 1696  cap = 2304

Go 1.18版本 之后擴容原理

和之前版本的區(qū)別,主要在擴容閾值,以及這行源碼:newcap += (newcap + 3*threshold) / 4。

在分配內存空間之前需要先確定新的切片容量,運行時根據(jù)切片的當前容量選擇不同的策略進行擴容:

1. 如果期望容量大于當前容量的兩倍就會使用期望容量;

2. 如果當前切片的長度小于閾值(默認 256)就會將容量翻倍;

3. 如果當前切片的長度大于等于閾值(默認 256),就會每次增加 25% 的容量,基準是 newcap + 3*threshold,直到新容量大于期望容量;

記錄容量變化如下:

[0 ->   -1] cap = 0     |  after append 0     cap = 1
[0 ->    0] cap = 1     |  after append 1     cap = 2   
[0 ->    1] cap = 2     |  after append 2     cap = 4   
[0 ->    3] cap = 4     |  after append 4     cap = 8   
[0 ->    7] cap = 8     |  after append 8     cap = 16  
[0 ->   15] cap = 16    |  after append 16    cap = 32  
[0 ->   31] cap = 32    |  after append 32    cap = 64  
[0 ->   63] cap = 64    |  after append 64    cap = 128 
[0 ->  127] cap = 128   |  after append 128   cap = 256 
[0 ->  255] cap = 256   |  after append 256   cap = 512 
[0 ->  511] cap = 512   |  after append 512   cap = 848 
[0 ->  847] cap = 848   |  after append 848   cap = 1280
[0 -> 1279] cap = 1280  |  after append 1280  cap = 1792
[0 -> 1791] cap = 1792  |  after append 1792  cap = 2560

大致規(guī)則如下:

其中,當擴容前容量 >= 256時,會按照公式進行擴容

newcap += (newcap + 3*threshold) / 4

這樣得到的預估容量并不是最終結果,還有內存對齊,進一步調整newcap

在1.18中,優(yōu)化了切片擴容的策略,讓底層數(shù)組大小的增長更加平滑:通過減小閾值并固定增加一個常數(shù),使得優(yōu)化后的擴容的系數(shù)在閾值前后不再會出現(xiàn)從2到1.25的突變,該commit作者給出了幾種原始容量下對應的“擴容系數(shù)”:

oldcap擴容系數(shù)
2562.0
5121.63
10241.44
20481.35
40961.30

可以看到,Go1.18的擴容策略中,隨著容量的增大,其擴容系數(shù)是越來越小的,可以更好地節(jié)省內存。

可以試著求一個極限,當oldcap遠大于256的時候,擴容系數(shù)將會變成1.25。

四、內存對齊

擴容之后的容量并不是嚴格按照這個策略的。那是為什么呢?

實際上,growslice? 的后半部分還有更進一步的優(yōu)化(內存對齊等),靠的是 roundupsize? 函數(shù),在計算完 newcap 值之后,還會有一個步驟計算最終的容量:

capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)

舉例:

還是上面的例子:

nums := []int{1, 2}
nums = append(nums, 2, 3, 4)
fmt.Printf("len:%v  cap:%v", len(nums), cap(nums))

按照上述策略的結果,應該是 len:5,cap:5。但是最終結果為 len:5,cap:6
解釋:容量計算完了后還要考慮到內存的高效利用,進行內存對齊,則會調用這個函數(shù) roundupsize 。

func roundupsize(size uintptr) uintptr {
    if size < _MaxSmallSize {
        if size <= smallSizeMax-8 {
            return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
        } else {
        return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
        }
    }
    if size+_PageSize < size {
        return size
    }
    return alignUp(size, _PageSize)
}

size 表示新切片需要的內存大小 我們傳入的 int 類型,每個占用 8 字節(jié) (可以調用 unsafe.Sizeof() 函數(shù)查看占用的大小),一共 5 個 所以是 40,size 小于_MaxSmallSize 并且小于 smallSizeMax-8 ,那么使用通用公式 (size+smallSizeDiv-1)/smallSizeDiv 計算得到 5,然后到 size_to_class8 找到第五號元素 為 4,再從 class_to_size 找到 第四號元素 為 48,這就是新切片占用的內存大小,每個 int 占用 8 字節(jié),所以最終切片的容量為 6 。所以說切片的擴容有它基本的擴容規(guī)則,在規(guī)則之后還要考慮內存對齊,這就代表不同數(shù)據(jù)類型的切片擴容的容量大小是會不一致。

五、總結

切片擴容通常是在進行切片的 append? 操作時觸發(fā)的。在進行 append? 操作時,如果切片容量不足以容納新的元素,就需要對切片進行擴容,此時就會調用 growslice 函數(shù)進行擴容。
切片擴容分兩個階段,分為 go1.18 之前和之后:

一、go1.18 之前:

1.如果期望容量大于當前容量的兩倍就會使用期望容量;
2.如果當前切片的長度小于 1024 就會將容量翻倍;
3.如果當前切片的長度大于 1024 就會每次增加 25% 的容量,直到新容量大于期望容量;

二、go1.18 之后:

1.如果期望容量大于當前容量的兩倍就會使用期望容量;

2.如果當前切片的長度小于閾值(默認 256)就會將容量翻倍;

3.如果當前切片的長度大于等于閾值(默認 256),就會每次增加 25% 的容量,基準是 newcap + 3*threshold,直到新容量大于期望容量;

總的來說,Go的設計者不斷優(yōu)化切片擴容的機制,其目的只有一個:就是控制讓小的切片容量增長速度快一點,減少內存分配次數(shù),而讓大切片容量增長率小一點,更好地節(jié)省內存。

  • 如果只選擇翻倍的擴容策略,那么對于較大的切片來說,現(xiàn)有的方法可以更好的節(jié)省內存。
  • 如果只選擇每次系數(shù)為1.25的擴容策略,那么對于較小的切片來說擴容會很低效。
  • 之所以選擇一個小于2的系數(shù),在擴容時被釋放的內存塊會在下一次擴容時更容易被重新利用。

以上就是Go語言切片擴容原理和過程的詳細內容,更多關于Go切片擴容的資料請關注腳本之家其它相關文章!

相關文章

  • Golang執(zhí)行go get私有庫提示

    Golang執(zhí)行go get私有庫提示"410 Gone" 的問題及解決辦法

    這篇文章主要介紹了Golang執(zhí)行go get私有庫提示”410 Gone“ 解決辦法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-02-02
  • 我放棄Python轉Go語言的9大理由(附優(yōu)秀書籍推薦)

    我放棄Python轉Go語言的9大理由(附優(yōu)秀書籍推薦)

    這篇文章主要給大家介紹了關于我放棄Python轉Go語言的9大理由,以及給大家推薦了6本優(yōu)秀的go語言書籍,對同樣想學習golang的朋友們具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2017-10-10
  • go語言中for?range使用方法及避坑指南

    go語言中for?range使用方法及避坑指南

    Go中的for range組合可以和方便的實現(xiàn)對一個數(shù)組或切片進行遍歷,但是在某些情況下使用for range時很可能就會被"坑",下面這篇文章主要給大家介紹了關于go語言中for?range使用方法及避坑指南的相關資料,需要的朋友可以參考下
    2022-09-09
  • Go語言標準庫flag的具體實現(xiàn)

    Go語言標準庫flag的具體實現(xiàn)

    Go語言的flag庫提供了一套簡單而強大的接口,用于解析命令行參數(shù),本文主要介紹了Go語言標準庫flag的具體實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • 一文帶你搞懂golang中內存分配逃逸分析

    一文帶你搞懂golang中內存分配逃逸分析

    這篇文章主要帶大家一起學習一下golang中內存分配逃逸分析,文中的示例代碼講解詳細,對我們深入了解golang有一定的幫助,感興趣的小伙伴可以了解下
    2023-08-08
  • go?原子操作的方式及實現(xiàn)原理全面深入解析

    go?原子操作的方式及實現(xiàn)原理全面深入解析

    這篇文章主要為大家介紹了go?原子操作的方式及實現(xiàn)原理深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • go gin中間件關于 c.next()、c.abort()和return的使用小結

    go gin中間件關于 c.next()、c.abort()和return的使用小結

    中間件的執(zhí)行順序是按照注冊順序執(zhí)行的,中間件可以通過 c.abort() + retrurn 來中止當前中間件,后續(xù)中間件和處理器的處理流程,?這篇文章給大家介紹go gin中間件關于 c.next()、c.abort()和return的使用小結,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Golang Map實現(xiàn)賦值和擴容的示例代碼

    Golang Map實現(xiàn)賦值和擴容的示例代碼

    這篇文章主要介紹了Golang Map實現(xiàn)賦值和擴容的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-04-04
  • golang字符串轉64位整數(shù)的示例代碼

    golang字符串轉64位整數(shù)的示例代碼

    這篇文章主要介紹了golang字符串轉64位整數(shù),在Go語言中,可以使用strconv包中的ParseInt函數(shù)將字符串轉換為64位整數(shù),本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2023-09-09
  • Go學習筆記之Zap日志的使用

    Go學習筆記之Zap日志的使用

    這篇文章主要為大家詳細介紹了Go語言中Zap日志的使用以及安裝,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,需要的可以參考一下
    2022-07-07

最新評論

普安县| 蕉岭县| 云梦县| 姚安县| 屏边| 昆山市| 上虞市| 本溪市| 府谷县| 晋宁县| 上犹县| 五寨县| 广宁县| 新巴尔虎左旗| 武义县| 抚州市| 乌拉特中旗| 交城县| 永德县| 绥中县| 枣强县| 霞浦县| 奉新县| 山东省| 柞水县| 正镶白旗| 吴江市| 宝应县| 宁国市| 东阳市| 额敏县| 民乐县| 汽车| 贵定县| 白水县| 和林格尔县| 鲁山县| 钟山县| 天长市| 德格县| 行唐县|