一文帶你搞懂Golang結(jié)構(gòu)體內(nèi)存布局
前言
結(jié)構(gòu)體在Go
語言中是一個很重要的部分,在項目中會經(jīng)常用到,大家在寫Go
時有沒有注意過,一個struct
所占的空間不一定等于各個字段加起來的空間之和,甚至有時候把字段的順序調(diào)整一下,struct
的所占空間不一樣,接下來通過這篇文章來看一下結(jié)構(gòu)體在內(nèi)存中是怎么分布的?通過對內(nèi)存布局的了解,可以幫助我們寫出更優(yōu)質(zhì)的代碼。感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
結(jié)構(gòu)體內(nèi)存布局
結(jié)構(gòu)體大小
結(jié)構(gòu)體實際上就是由各種類型的數(shù)據(jù)組合而成的一種符合數(shù)據(jù)類型,一個結(jié)構(gòu)體變量的大小是由結(jié)構(gòu)體中的字段決定。結(jié)構(gòu)體和它所包含的數(shù)據(jù)在內(nèi)存中是以連續(xù)塊的形式存在的。我們可以借助unsafe.Sizeof
方法,來獲?。?/p>
package main import ( "fmt" "unsafe" ) type Test struct { T1 int8 // 1 T2 int8 // 1 T3 int8 // 1 } func main() { var t Test fmt.Println(unsafe.Sizeof(t)) //3 bytes }
內(nèi)存對齊
不同類型的變量占用內(nèi)存大小是不一樣的,但是cpu每次讀取的內(nèi)存長度是固定的(比如cpu是64位的,一次可以從內(nèi)存中讀取64位的數(shù)據(jù),即8個字節(jié)),為了cpu能高效的讀寫數(shù)據(jù),編譯器會把各種類型的數(shù)據(jù)放在合適的地址,而不是順序的一個接一個的排放,并占用合適的長度,這就是內(nèi)存對齊。每種類型的對齊值就是它的對齊邊界。
示例:
package main import ( "fmt" "unsafe" ) type Test struct { a int8 // 1 b int64 // 8 c int32 // 4 } type Test2 struct { a int8 // 1 b int32 // 4 c int64 // 8 } func main() { var t Test fmt.Println(unsafe.Sizeof(t)) // 24 var t2 Test2 fmt.Println(unsafe.Sizeof(t2))// 16 }
通過上面示例,我們可以看到兩個結(jié)構(gòu)體中3個字段類型相同,當(dāng)排列順序發(fā)生變化時,總的內(nèi)存大小也會發(fā)生變化。下面我們來一起分析一下:
如果沒有內(nèi)存對齊,那結(jié)構(gòu)體各個字段在內(nèi)存中是緊密排列的,如t1內(nèi)存布局示意圖如下:
因為b這個字段需要8個字節(jié),所以會有一個字節(jié)的數(shù)據(jù)排列到第2個字中。如果程序想要讀取b字段的數(shù)據(jù),那么CPU需要兩次讀取才能獲取到完整的數(shù)據(jù),這樣就會影響了程序的性能。
所以,為了能讓CPU減少一次獲取的時間,Go編譯器會幫你把struct結(jié)構(gòu)體做數(shù)據(jù)的對齊,以便CPU可以一次將該數(shù)據(jù)從內(nèi)存中讀取出來。重新排列后內(nèi)存布局結(jié)構(gòu)示意圖如下:
其中有13個字節(jié)是真正存儲數(shù)據(jù)的,而灰色的11個字節(jié)則是為了對齊而填充上的,不存儲任何數(shù)據(jù),所以才會比沒有對齊排列時多出11個字節(jié)。
雖然通過對齊填充的方式提高了CPU讀寫數(shù)據(jù)的效率,但是這些填充內(nèi)存確實有點(diǎn)浪費(fèi)空間,那有沒有辦法既可以既可以做到內(nèi)存對齊保證CPU讀寫效率又能減少浪費(fèi)內(nèi)存空間呢?
那就是調(diào)整struct字段的順序,我們在來看一下t2結(jié)構(gòu)體的字段內(nèi)存布局結(jié)構(gòu)示意圖如下:
這樣重新排列后,只占了16個字節(jié),比上面那種方式少了8個字節(jié)。由此可知,對結(jié)構(gòu)體字段的重新排列會讓結(jié)構(gòu)體更節(jié)省內(nèi)。
總結(jié)
本篇文章我們一起學(xué)習(xí)了Go 語言中的內(nèi)存對齊,主要內(nèi)容如下:
- 結(jié)構(gòu)體是占用一塊連續(xù)的內(nèi)存,一個結(jié)構(gòu)體變量的大小是由結(jié)構(gòu)體中的字段決定。
- unsafe.Sizeof(x) 返回了變量x的內(nèi)存占用大小。
- 兩個結(jié)構(gòu)體,即使包含變量類型的數(shù)量相同,但是位置不同,占用的內(nèi)存大小也不同,由此引出了內(nèi)存對齊。
- 對結(jié)構(gòu)體字段的重新排列會讓結(jié)構(gòu)體更節(jié)省內(nèi)。
到此這篇關(guān)于一文帶你搞懂Golang結(jié)構(gòu)體內(nèi)存布局的文章就介紹到這了,更多相關(guān)Golang結(jié)構(gòu)體內(nèi)存布局內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!