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

Go源碼分析之預分配slice內存

 更新時間:2023年08月15日 08:49:11   作者:Goland貓  
這篇文章主要從Go語言源碼帶大家分析一下預分配slice內存的相關知識,文中的示例代碼簡潔易懂,對我們深入了解go有一定的幫助,需要的可以學習一下

切片擴容

在 1.18 版本前,切片擴容,在容量小于1024時,以2倍大小擴容。超過1024后,以1.25倍擴容。在擴容后切片的基礎上,會根據長度和容量進行 roundupsize 。

在1.18版本后,接下來看一下源碼如下:

func growslice(et *_type, old slice, cap int) slice {
	//  ......
	newcap := old.cap
	doublecap := newcap + newcap //雙倍擴容(原容量的兩倍)
	if cap > doublecap {         //如果所需容量大于 兩倍擴容,則直接擴容到所需容量
		newcap = cap
	} else {
		const threshold = 256    //這里設置了一個 閾值 -- 256
		if old.cap < threshold { //如果舊容量 小于 256,則兩倍擴容
			newcap = doublecap
		} else {
			// 檢查 0 < newcap 以檢測溢出并防止無限循環(huán)。
			for 0 < newcap && newcap < cap { //如果新容量 > 0  并且 原容量 小于 所需容量
				// 從小片的增長2x過渡到大片的增長1.25x。這個公式給出了兩者之間的平滑過渡。
				newcap += (newcap + 3*threshold) / 4
				//新容量是 = 1.25 原容量 + 3/4 閾值 (192)
			}
			//當newcap計算溢出時,將newcap設置為請求的上限。
			if newcap <= 0 { // 如果發(fā)生了溢出,將新容量設置為請求的容量大小
				newcap = cap
			}
		}
	}
}

函數判斷如果所需容量 cap 大于兩倍擴容的容量 doublecap,說明 cap 的需求已經超過了兩倍擴容的范圍,所以將 newcap 直接設為 cap。

否則,如果原容量 old.cap 小于一個閾值 threshold(這里設為256),則將 newcap 設置為原容量的兩倍 doublecap

如果既不滿足上述條件,則進入一個循環(huán),只要 newcap 大于0且小于所需容量 cap,就會進入循環(huán)。在每次循環(huán)迭代中,newcap 的增長方式為當前 newcap 加上 3*threshold 的四分之一。這個計算方式使得容量的增長逐漸從原容量的兩倍過渡到1.25倍,實現了一個平滑的過渡。

最后,在循環(huán)結束后,判斷如果 newcap 仍然小于等于0(溢出情況),則將 newcap 設為所需容量 cap

如果 現有容量 小于 256 ,則新容量是原來的兩倍

新容量 = 1.25 原容量 + 3/4 閾值 (192) “這個公式給出了從1.25倍增長 過渡到2 倍增長,兩者之間的平滑過渡。” 在此情況下,如果發(fā)生了溢出,將新容量設置為請求的容量大小

代碼測試

func main() {
	slice := make([]int, 0)
	for i := 0; i < 512; i++ {
		slice = append(slice, i)
	}
	newSlice := append(slice, 5000)
	fmt.Printf("Before Pointer = %p, len = %d, cap = %d\n", &slice, len(slice), cap(slice))
	fmt.Printf("Before Pointer = %p, len = %d, cap = %d\n", &newSlice, len(newSlice), cap(newSlice))
}

執(zhí)行輸出如下:

再看一個例子

下面用 go1.17 和 go1.18 兩個版本來分開說明。先通過一段測試代碼,直觀感受一下兩個版本在擴容上的區(qū)別。

package?main  
import?"fmt"  
func?main()?{  
????s?:=?make([]int,?0)  
????oldCap?:=?cap(s)  
????for?i?:=?0;?i?<?2048;?i++?{  
????????s?=?append(s,?i)  
????????newCap?:=?cap(s)  
????????if?newCap?!=?oldCap?{  
????????????fmt.Printf("[%d?->?%4d]?cap?=?%-4d??|??after?append?%-4d??cap?=?%-4d\n",?0,?i-1,?oldCap,?i,?newCap)  
????????????oldCap?=?newCap  
????????}  
????}  
}

運行結果(1.17 版本):

[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

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

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

運行結果(1.18 版本):

[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

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

內存對齊

但是,后半部分還對 newcap 作了一個內存對齊,這個和內存分配策略相關。進行內存對齊之后,新 slice 的容量是要 大于等于 按照前半部分生成的newcap。

之后,向 Go 內存管理器申請內存,將老 slice 中的數據復制過去,并且將 append 的元素添加到新的底層數組中。

最后,向 growslice 函數調用者返回一個新的 slice,這個 slice 的長度并沒有變化,而容量卻增大了。

測試代碼

import "fmt"
func main() {
	s := []int{1,2}
	s = append(s,4,5,6)
	fmt.Printf("len=%d, cap=%d",len(s),cap(s))
}

輸出

len=5, cap=6

根據Go語言中切片的擴容機制,當切片容量不足以容納額外的元素時,它會自動進行擴容。在這種情況下,切片的容量會根據需要自動增長,通常會以原來容量的2倍進行擴容。

根據您的代碼,初始切片s的容量為2,添加了3個元素后,長度變?yōu)?。實際上,在這種情況下,切片的容量不會立即擴大到8,而是繼續(xù)保持為6。這是因為Go語言的切片擴容機制會優(yōu)化以減少內存的浪費。擴容時,切片會選擇一個合適的容量,使得容量盡可能靠近但大于所需的最小容量。

源碼分析

func growslice(et *_type, old slice, cap int) slice {
    // ……
    newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		// ……
	}
	// ……
	capmem = roundupsize(uintptr(newcap) * ptrSize)
	newcap = int(capmem / ptrSize)
}

capmem = roundupsize(uintptr(newcap) * ptrSize):根據 newcap 和指針的大小計算需要分配的內存大小。 newcap = int(capmem / ptrSize):通過將內存大小除以指針大小,將新的容量 newcap 轉換為整數。

到此這篇關于Go源碼分析之預分配slice內存的文章就介紹到這了,更多相關Go預分配slice內存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • go nil處理如何正確返回nil的error

    go nil處理如何正確返回nil的error

    這篇文章主要為大家介紹了go中的nil處理,如何正確返回nil的error實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go語言題解LeetCode268丟失的數字示例詳解

    Go語言題解LeetCode268丟失的數字示例詳解

    這篇文章主要為大家介紹了Go語言題解LeetCode268丟失的數字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Golang在圖像中繪制矩形框的示例代碼

    Golang在圖像中繪制矩形框的示例代碼

    這篇文章主要介紹了Golang在圖像中繪制矩形框的示例代碼,文中有詳細的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下
    2008-08-08
  • 深入理解GoLang并發(fā)與并行

    深入理解GoLang并發(fā)與并行

    本文主要介紹了GoLang并發(fā)與并行,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-03-03
  • GoLang unsafe包詳細講解

    GoLang unsafe包詳細講解

    從golang的定義來看,unsafe 是類型安全的操作。顧名思義,它應該非常謹慎地使用; unsafe可能很危險,但也可能非常有用。例如,當使用系統(tǒng)調用和Go結構必須具有與C結構相同的內存布局時,您可能別無選擇,只能使用unsafe
    2022-10-10
  • go語言用八百行代碼實現一個JSON解析器

    go語言用八百行代碼實現一個JSON解析器

    這篇文章主要為大家介紹了go語言用八百行代碼實現一個JSON解析器實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • Golang?RPC的原理與簡單調用詳解

    Golang?RPC的原理與簡單調用詳解

    RPC(Remote?Procedure?Call),主要是幫助我們屏蔽網絡編程細節(jié)?,使我們更專注于業(yè)務邏輯,所以本文主要來和大家聊聊RPC的原理與簡單調用,希望對大家有所幫助
    2023-05-05
  • GO中sync包自由控制并發(fā)示例詳解

    GO中sync包自由控制并發(fā)示例詳解

    這篇文章主要為大家介紹了GO中sync包自由控制并發(fā)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 關于go語言載入json可能遇到的一個坑

    關于go語言載入json可能遇到的一個坑

    Go 語言從新手到大神,每個人多少都會踩一些坑,那么下面這篇文章主要給大家介紹了關于go語言載入json可能遇到的一個坑,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-07-07
  • gin自定義中間件解決requestBody不可重讀(請求體取值)

    gin自定義中間件解決requestBody不可重讀(請求體取值)

    這篇文章主要介紹了gin自定義中間件解決requestBody不可重讀,確??刂破髂軌颢@取請求體值,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10

最新評論

武安市| 尼玛县| 运城市| 清河县| 冀州市| 凉山| 平陆县| 临西县| 皋兰县| 松潘县| 天门市| 建湖县| 繁峙县| 尼木县| 横峰县| 屏边| 四会市| 来安县| 天水市| 雅江县| 柳河县| 龙州县| 正蓝旗| 汝阳县| 呼图壁县| 德阳市| 上饶市| 商南县| 法库县| 阳朔县| 金平| 汶上县| 吴桥县| 庆元县| 凤凰县| 吴旗县| 香河县| 富阳市| 西华县| 乐昌市| 天柱县|