Go內(nèi)存分配之結(jié)構(gòu)體優(yōu)化技巧
在使用Golang進(jìn)行內(nèi)存分配時(shí),我們需要遵循一系列規(guī)則。在深入了解這些規(guī)則之前,我們需要先了解變量的對(duì)齊方式。
Golang的unsafe
包中有一個(gè)函數(shù)Alignof
,簽名如下:
func Alignof(x ArbitraryType) uintptr
對(duì)于任何類型為v
的變量x
,AlignOf
函數(shù)會(huì)返回該變量的對(duì)齊方式。我們將對(duì)齊方式記為m
?,F(xiàn)在,Golang確保m
是滿足變量x的內(nèi)存地址 % m == 0
的最大可能數(shù),也就是說,變量x的內(nèi)存地址是m的倍數(shù)。
讓我們來看看一些數(shù)據(jù)類型的對(duì)齊方式:
byte
,int8
,uint8
-> 1int16
,uint16
-> 2int32
,uint32
,float32
,complex64
-> 4int
,int64
,uint64
,float64
,complex128
-> 8string
,slice
-> 8
對(duì)于結(jié)構(gòu)體中的字段,行為可能會(huì)有所不同,詳細(xì)信息請(qǐng)參考包的文檔。
為了更好地理解結(jié)構(gòu)體內(nèi)存分配的情況,我們將使用unsafe
包中的另一個(gè)函數(shù)Offsetof
。該函數(shù)返回字段相對(duì)于結(jié)構(gòu)體起始位置的位置,換句話說,它返回字段起始位置與結(jié)構(gòu)體起始位置之間的字節(jié)數(shù)。
func Offsetof(x ArbitraryType) uintptr
為了更好地理解結(jié)構(gòu)體內(nèi)存分配,讓我們以一個(gè)示例結(jié)構(gòu)體為例:
type Example struct { a int8 b string c int8 d int32 }
現(xiàn)在,我們將找出類型為Example
的變量所占用的總內(nèi)存,并嘗試優(yōu)化分配。
var v = Example{ a: 10, b: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus rhoncus.", c: 20, d: 100, } fmt.Println("字段a的偏移量:", unsafe.Offsetof(v.a)) // 輸出:0 fmt.Println("字段b的偏移量:", unsafe.Offsetof(v.b)) // 輸出:8 fmt.Println("字段c的偏移量:", unsafe.Offsetof(v.c)) // 輸出:24 fmt.Println("字段d的偏移量:", unsafe.Offsetof(v.d)) // 輸出:28
現(xiàn)在,問題出現(xiàn)了:“為什么結(jié)構(gòu)體中字段b的偏移量是8?它應(yīng)該是1,因?yàn)樽侄蝍的類型是int8,只占用1個(gè)字節(jié)。”回到字符串?dāng)?shù)據(jù)類型的對(duì)齊方式,它的值為8,這意味著地址需要被8整除,因此在其中插入了7個(gè)字節(jié)的“填充”,以確保這種行為。
為什么字段c的偏移量是24?字段b中的字符串看起來比16個(gè)字節(jié)要長得多,如果字符串的偏移量是8,那么字段c的偏移量應(yīng)該更大一些。
上述問題的答案是,在Go中,字符串并不是在結(jié)構(gòu)體內(nèi)的同一位置分配內(nèi)存的。有一個(gè)單獨(dú)的數(shù)據(jù)結(jié)構(gòu)來保存字符串描述符,并且該字符串描述符以原地方式存儲(chǔ)在結(jié)構(gòu)體中,用于類型為string的字段,該描述符的大小為16個(gè)字節(jié)。
現(xiàn)在,讓我們來看看unsafe
包中的另一個(gè)函數(shù)Sizeof
。正如其名稱所示,該函數(shù)估計(jì)并返回類型為x的變量所占用的字節(jié)數(shù)。
注意:它是根據(jù)結(jié)構(gòu)體中可能存在的不同大小的字段來估計(jì)大小的。
func Sizeof(x ArbitraryType) uintptr
現(xiàn)在,讓我們來看看我們的結(jié)構(gòu)體Example的大小。
fmt.Println("Example的大?。?, unsafe.Sizeof(v)) // 輸出:32
我們?nèi)绾蝺?yōu)化這個(gè)結(jié)構(gòu)體以最小化填充呢?
為了優(yōu)化這個(gè)結(jié)構(gòu)體的內(nèi)存,我們將查看不同數(shù)據(jù)類型的對(duì)齊方式,并嘗試減少填充。讓我們嘗試將兩個(gè)int8類型的字段放在一起。
type y struct { a int8 c int8 b string d int32 } var v = y{} fmt.Println("字段a的偏移量:", unsafe.Offsetof(v.a)) // 輸出:0 fmt.Println("字段b的偏移量:", unsafe.Offsetof(v.b)) // 輸出:8 fmt.Println("字段c的偏移量:", unsafe.Offsetof(v.c)) // 輸出:1 fmt.Println("字段d的偏移量:", unsafe.Offsetof(v.d)) // 輸出:24 fmt.Println("Example的大?。?, unsafe.Sizeof(v)) // 輸出:32
太棒了,我們?nèi)サ袅艘恍┨畛?,但是為什么大小仍然?2?大小應(yīng)該是1(a)+ 1(c)+ 6(填充)+ 16(b)+ 4(d)= 28
現(xiàn)在,當(dāng)結(jié)構(gòu)體的最后一個(gè)字段與架構(gòu)的對(duì)齊要求不完全一致時(shí),會(huì)在最后一個(gè)字段之后添加填充,以確保結(jié)構(gòu)體的整體大小是其字段中最大對(duì)齊要求的倍數(shù)。因?yàn)樽址當(dāng)?shù)據(jù)類型的最大對(duì)齊方式為8,所以額外添加了填充,使大小成為8的倍數(shù),即在末尾填充了4個(gè)字節(jié),使大小為32字節(jié)。
我們能否進(jìn)一步減少填充,使其更加優(yōu)化?
讓我們嘗試通過移動(dòng)字段位置來實(shí)現(xiàn)。
type y struct { b string d int32 a int8 c int8 } var v = y{} fmt.Println("字段a的偏移量:", unsafe.Offsetof(v.a)) // 輸出:20 fmt.Println("字段b的偏移量:", unsafe.Offsetof(v.b)) // 輸出:0 fmt.Println("字段c的偏移量:", unsafe.Offsetof(v.c)) // 輸出:21 fmt.Println("字段d的偏移量:", unsafe.Offsetof(v.d)) // 輸出:16 fmt.Println("Example的大?。?, unsafe.Sizeof(v)) // 輸出:24
我們可以看到,通過重新排列字段的位置,使得對(duì)齊需要最小化填充,我們已經(jīng)將結(jié)構(gòu)體的大小從32減小到24,這是內(nèi)存優(yōu)化的巨大進(jìn)步,達(dá)到了25%。
當(dāng)前的內(nèi)存占用是16(b)+ 4(d)+ 1(a)+ 1(b)+ 2(填充)。
遺憾的是,由于語言和架構(gòu)的限制,我們無法進(jìn)一步去除填充。
到此這篇關(guān)于Go內(nèi)存分配之結(jié)構(gòu)體優(yōu)化技巧的文章就介紹到這了,更多相關(guān)Go結(jié)構(gòu)體內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在?Golang?中使用?Cobra?創(chuàng)建?CLI?應(yīng)用
這篇文章主要介紹了在?Golang?中使用?Cobra?創(chuàng)建?CLI?應(yīng)用,來看下?Cobra?的使用,這里我們使用的?go1.13.3?版本,使用?Go?Modules?來進(jìn)行包管理,需要的朋友可以參考下2022-01-01golang數(shù)組和切片作為參數(shù)和返回值的實(shí)現(xiàn)
本文主要介紹了golang數(shù)組和切片作為參數(shù)和返回值的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Golang正整數(shù)指定規(guī)則排序算法問題分析
這篇文章主要介紹了Golang正整數(shù)指定規(guī)則排序算法問題,結(jié)合實(shí)例形式分析了Go語言排序算法操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01