golang內(nèi)存對齊詳解
背景
在golang中,每一種數(shù)據(jù)類型都有其對應(yīng)的數(shù)據(jù)類型大小,也就是占用了多少內(nèi)存空間
我們可以通過unsafe.Sizeof函數(shù),來確定一個(gè)變量占用的內(nèi)存字節(jié)數(shù)
demo:
package main import ( "fmt" "testing" "unsafe" ) func TestTypeSize(t *testing.T) { var a int8 = 4 s1 := "hello world" s2 := "hahaha" fmt.Println(unsafe.Sizeof(a)) // 1字節(jié) fmt.Println(unsafe.Sizeof(s1)) // 16字節(jié) fmt.Println(unsafe.Sizeof(s2)) // 16字節(jié) }
注意:
- unsafe.Sizeof返回的是一個(gè)變量占用的內(nèi)存字節(jié)數(shù),而不是變量所表示內(nèi)容占用的內(nèi)存字節(jié)數(shù)。所以在上述demo中,盡管s1和s2的字符串內(nèi)容不一樣,但s1和s2的變量類型都是string,所以s1和s2占用的內(nèi)存字節(jié)數(shù)相同
結(jié)構(gòu)體大小
我們還可以通過unsafe.Sizeof來獲取結(jié)構(gòu)體占用的內(nèi)存字節(jié)數(shù)
demo1:
package main import ( "fmt" "testing" "unsafe" ) type demo1 struct { a int8 b int16 } func TestStructSize1(t *testing.T) { d1 := demo1{} fmt.Println(unsafe.Sizeof(d1.a)) // 1字節(jié) fmt.Println(unsafe.Sizeof(d1.b)) // 2字節(jié) fmt.Println(unsafe.Sizeof(d1)) // 4字節(jié) }
問題1:
- 結(jié)構(gòu)體占用的內(nèi)存字節(jié)數(shù)不等于結(jié)構(gòu)體內(nèi)各個(gè)字段占用的內(nèi)存字節(jié)數(shù)之和。結(jié)構(gòu)體內(nèi)各個(gè)字段占用的內(nèi)存字節(jié)數(shù)之和為:3字節(jié) = 1字節(jié)(a占用字節(jié)數(shù)) + 2字節(jié)(b占用字節(jié)數(shù)),結(jié)構(gòu)體占用字節(jié)數(shù)為4字節(jié)
demo2:
package main import ( "fmt" "testing" "unsafe" ) type demo2 struct { a int8 b int16 c int32 d int64 } type demo3 struct { a int8 d int64 b int16 c int32 } func TestStructSize2(t *testing.T) { d2 := demo2{} fmt.Println(unsafe.Sizeof(d2.a)) // 1字節(jié) fmt.Println(unsafe.Sizeof(d2.b)) // 2字節(jié) fmt.Println(unsafe.Sizeof(d2.c)) // 4字節(jié) fmt.Println(unsafe.Sizeof(d2.d)) // 8字節(jié) fmt.Println(unsafe.Sizeof(d2)) // 16字節(jié) d3 := demo3{} fmt.Println(unsafe.Sizeof(d3.a)) // 1字節(jié) fmt.Println(unsafe.Sizeof(d3.b)) // 2字節(jié) fmt.Println(unsafe.Sizeof(d3.c)) // 4字節(jié) fmt.Println(unsafe.Sizeof(d3.d)) // 8字節(jié) fmt.Println(unsafe.Sizeof(d3)) // 24字節(jié) }
問題2:
- 當(dāng)兩個(gè)結(jié)構(gòu)體內(nèi)的字段類型一樣時(shí),字段順序不同時(shí),結(jié)構(gòu)體占用的字節(jié)數(shù)也可能不同
出現(xiàn)上面兩個(gè)問題的根本原因,就是本文要討論的內(nèi)容:內(nèi)存對齊
什么是內(nèi)存對齊
現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定變量的時(shí)候經(jīng)常在特定的內(nèi)存地址訪問,這就需要各類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對齊
簡單來說,內(nèi)存對齊就是把各種數(shù)據(jù)類型按照一定的規(guī)則,在內(nèi)存空間進(jìn)行排列,而不是直接按照順序進(jìn)行排列
為什么需要內(nèi)存對齊
那么為什么需要進(jìn)行內(nèi)存對齊呢,主要原因有以下幾點(diǎn):
- 性能原因:CPU為了加速對內(nèi)存的訪問速度,并不是一個(gè)字節(jié)一個(gè)字節(jié)的去訪問內(nèi)存,而是一次訪問多個(gè)字節(jié),一般稱之為字長。32位CUP的字長一般是4字節(jié),64位CPU的字長一般是8字節(jié)。如果沒有進(jìn)行內(nèi)存對齊,那么CPU在訪問一個(gè)變量時(shí),可能需要進(jìn)行多次讀取,然后進(jìn)行拼接,才能得到最終的變量內(nèi)容。進(jìn)行內(nèi)存對齊后,可以減少CPU訪問內(nèi)存次數(shù),提升性能
- 更好的保證訪問的原子性
- 平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常
如何進(jìn)行內(nèi)存對齊
常見數(shù)據(jù)類型的內(nèi)存對齊
編譯器按照每種數(shù)據(jù)類型的對齊邊界來進(jìn)行內(nèi)存對齊
首先需要確認(rèn)每種數(shù)據(jù)類型的對齊邊界,對齊邊界 = min(類型大小,平臺字長)
常見數(shù)據(jù)類型在常見平臺上的對齊邊界:
類型 | 類型大小 | 64位平臺字長 | 64位平臺對齊邊界 | 32位平臺字長 | 32位平臺對齊邊界 |
---|---|---|---|---|---|
int8 | 1byte | 8byte | 1byte | 4byte | 1byte |
int16 | 2byte | 8byte | 2byte | 4byte | 2byte |
int32 | 4byte | 8byte | 4byte | 4byte | 4byte |
int64 | 8byte | 8byte | 8byte | 4byte | 4byte |
string | 16byte | 8byte | 8byte | 4byte | 4byte |
為什么對齊邊界需要取類型大小和平臺字長的最小值呢?
答案是為了節(jié)省內(nèi)存,避免內(nèi)存浪費(fèi),提升讀取的性能
- 當(dāng)類型大小 < 平臺字長時(shí),int8類型在64位平臺進(jìn)行內(nèi)存對齊兩種情況如圖:
- 當(dāng)類型大小 > 平臺字長時(shí),int64類型在32位平臺進(jìn)行內(nèi)存對齊兩種情況如圖:
結(jié)構(gòu)體的內(nèi)存對齊
結(jié)構(gòu)體的對齊邊界為:結(jié)構(gòu)體內(nèi)成員類型最大的對齊邊界
結(jié)構(gòu)體進(jìn)行內(nèi)存對齊的兩個(gè)要求:
- 起始地址是結(jié)構(gòu)體對齊邊界的倍數(shù)
- 結(jié)構(gòu)體整體占用字節(jié)數(shù)必須是結(jié)構(gòu)體對齊邊界的倍數(shù)。為了保證結(jié)構(gòu)體數(shù)組的內(nèi)存對齊
下面我們具體分析下結(jié)構(gòu)體的內(nèi)存對齊
type demo4 struct { a int8 b int64 c int32 d int16 }
首先確定結(jié)構(gòu)體demo4的對齊邊界為:成員類型最大的對齊邊界 = 8byte
結(jié)構(gòu)體內(nèi)存對齊如圖:
結(jié)構(gòu)體內(nèi)存對齊的特殊情況
如果結(jié)構(gòu)體的字段包含空結(jié)構(gòu)體類型時(shí)
- 空結(jié)構(gòu)體類型字段不是最后一個(gè)字段時(shí),不會占用內(nèi)存
- 空結(jié)構(gòu)體類型字段是最后一個(gè)字段時(shí),需要進(jìn)行內(nèi)存對齊,占用的內(nèi)存大小和前一個(gè)字段的大小一致
demo:
package main import ( "fmt" "testing" "unsafe" ) type demo5 struct { s struct{} a int8 } type demo6 struct { a int8 s struct{} } func TestStructSize3(t *testing.T) { d5 := demo5{} fmt.Println(unsafe.Sizeof(d5.a)) // 1字節(jié) fmt.Println(unsafe.Sizeof(d5.s)) // 0字節(jié) fmt.Println(unsafe.Sizeof(d5)) // 1字節(jié) d6 := demo6{} fmt.Println(unsafe.Sizeof(d6.a)) // 1字節(jié) fmt.Println(unsafe.Sizeof(d6.s)) // 0字節(jié) fmt.Println(unsafe.Sizeof(d6)) // 2字節(jié) }
空結(jié)構(gòu)體類型字段是最后一個(gè)字段時(shí),需要占用內(nèi)存,主要還是為了解決內(nèi)存泄漏問題
內(nèi)存泄漏問題分析:
以上就是golang內(nèi)存對齊詳解的詳細(xì)內(nèi)容,更多關(guān)于golang內(nèi)存對齊的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang中slice切片的實(shí)現(xiàn)示例
Go語言中,切片是對數(shù)組的抽象,提供了更靈活的動態(tài)數(shù)組解決方案,本文就來介紹一下Golang中slice切片的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-09-09Go使用database/sql操作數(shù)據(jù)庫的教程指南
Go?語言中,有一個(gè)名為database/sql的標(biāo)準(zhǔn)庫,提供了統(tǒng)一的編程接口,使開發(fā)人員能夠以一種通用的方式與各種關(guān)系型數(shù)據(jù)庫進(jìn)行交互,本文就來和大家講講它的具體操作吧2023-06-06重學(xué)Go語言之?dāng)?shù)組的具體使用詳解
Go的數(shù)組是一種復(fù)合數(shù)據(jù)類型,在平時(shí)開發(fā)中并不常用,更常用的是切片(slice),可以把切片看作是能動態(tài)擴(kuò)容的數(shù)組,切片的底層數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,所以數(shù)組雖不常用,但仍然有必要掌握2023-02-02