golang內(nèi)存對(duì)齊詳解
背景
在golang中,每一種數(shù)據(jù)類型都有其對(duì)應(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)存對(duì)齊
什么是內(nèi)存對(duì)齊
現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對(duì)任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定變量的時(shí)候經(jīng)常在特定的內(nèi)存地址訪問,這就需要各類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊
簡(jiǎn)單來說,內(nèi)存對(duì)齊就是把各種數(shù)據(jù)類型按照一定的規(guī)則,在內(nèi)存空間進(jìn)行排列,而不是直接按照順序進(jìn)行排列
為什么需要內(nèi)存對(duì)齊
那么為什么需要進(jìn)行內(nèi)存對(duì)齊呢,主要原因有以下幾點(diǎn):
- 性能原因:CPU為了加速對(duì)內(nèi)存的訪問速度,并不是一個(gè)字節(jié)一個(gè)字節(jié)的去訪問內(nèi)存,而是一次訪問多個(gè)字節(jié),一般稱之為字長(zhǎng)。32位CUP的字長(zhǎng)一般是4字節(jié),64位CPU的字長(zhǎng)一般是8字節(jié)。如果沒有進(jìn)行內(nèi)存對(duì)齊,那么CPU在訪問一個(gè)變量時(shí),可能需要進(jìn)行多次讀取,然后進(jìn)行拼接,才能得到最終的變量?jī)?nèi)容。進(jìn)行內(nèi)存對(duì)齊后,可以減少CPU訪問內(nèi)存次數(shù),提升性能

- 更好的保證訪問的原子性

- 平臺(tái)原因(移植原因):不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常
如何進(jìn)行內(nèi)存對(duì)齊
常見數(shù)據(jù)類型的內(nèi)存對(duì)齊
編譯器按照每種數(shù)據(jù)類型的對(duì)齊邊界來進(jìn)行內(nèi)存對(duì)齊
首先需要確認(rèn)每種數(shù)據(jù)類型的對(duì)齊邊界,對(duì)齊邊界 = min(類型大小,平臺(tái)字長(zhǎng))
常見數(shù)據(jù)類型在常見平臺(tái)上的對(duì)齊邊界:
| 類型 | 類型大小 | 64位平臺(tái)字長(zhǎng) | 64位平臺(tái)對(duì)齊邊界 | 32位平臺(tái)字長(zhǎng) | 32位平臺(tái)對(duì)齊邊界 |
|---|---|---|---|---|---|
| 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 |
為什么對(duì)齊邊界需要取類型大小和平臺(tái)字長(zhǎng)的最小值呢?
答案是為了節(jié)省內(nèi)存,避免內(nèi)存浪費(fèi),提升讀取的性能
- 當(dāng)類型大小 < 平臺(tái)字長(zhǎng)時(shí),int8類型在64位平臺(tái)進(jìn)行內(nèi)存對(duì)齊兩種情況如圖:

- 當(dāng)類型大小 > 平臺(tái)字長(zhǎng)時(shí),int64類型在32位平臺(tái)進(jìn)行內(nèi)存對(duì)齊兩種情況如圖:

結(jié)構(gòu)體的內(nèi)存對(duì)齊
結(jié)構(gòu)體的對(duì)齊邊界為:結(jié)構(gòu)體內(nèi)成員類型最大的對(duì)齊邊界
結(jié)構(gòu)體進(jìn)行內(nèi)存對(duì)齊的兩個(gè)要求:
- 起始地址是結(jié)構(gòu)體對(duì)齊邊界的倍數(shù)
- 結(jié)構(gòu)體整體占用字節(jié)數(shù)必須是結(jié)構(gòu)體對(duì)齊邊界的倍數(shù)。為了保證結(jié)構(gòu)體數(shù)組的內(nèi)存對(duì)齊
下面我們具體分析下結(jié)構(gòu)體的內(nèi)存對(duì)齊
type demo4 struct {
a int8
b int64
c int32
d int16
}首先確定結(jié)構(gòu)體demo4的對(duì)齊邊界為:成員類型最大的對(duì)齊邊界 = 8byte
結(jié)構(gòu)體內(nèi)存對(duì)齊如圖:

結(jié)構(gòu)體內(nèi)存對(duì)齊的特殊情況
如果結(jié)構(gòu)體的字段包含空結(jié)構(gòu)體類型時(shí)
- 空結(jié)構(gòu)體類型字段不是最后一個(gè)字段時(shí),不會(huì)占用內(nèi)存
- 空結(jié)構(gòu)體類型字段是最后一個(gè)字段時(shí),需要進(jìn)行內(nèi)存對(duì)齊,占用的內(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)存對(duì)齊詳解的詳細(xì)內(nèi)容,更多關(guān)于golang內(nèi)存對(duì)齊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
GoLang string與strings.Builder使用對(duì)比詳解
這篇文章主要介紹了GoLang string與strings.Builder使用對(duì)比,Builder 用于使用 Write 方法有效地構(gòu)建字符串。它最大限度地減少了內(nèi)存復(fù)制。零值可以使用了。不要復(fù)制非零生成器2023-03-03
在Golang中實(shí)現(xiàn)定時(shí)任務(wù)的幾種高效方法
本文將詳細(xì)介紹在Golang中實(shí)現(xiàn)定時(shí)任務(wù)的幾種高效方法,包括time包中的Ticker和Timer、第三方庫(kù)cron的使用,以及基于channel和goroutine的自定義實(shí)現(xiàn),我們將通過實(shí)際代碼示例和性能分析,幫助開發(fā)者選擇最適合自己場(chǎng)景的定時(shí)任務(wù)解決方案,需要的朋友可以參考下2025-06-06
go語言奇偶轉(zhuǎn)置排序算法實(shí)現(xiàn)方法(附帶源碼)
這篇文章主要介紹了go語言奇偶轉(zhuǎn)置排序算法實(shí)現(xiàn)方法的相關(guān)資料,這種算法是一種專門為并行計(jì)算環(huán)境設(shè)計(jì)的排序算法,它通過交替執(zhí)行奇數(shù)索引和偶數(shù)索引的比較來排序,需要的朋友可以參考下2026-02-02
go語言開發(fā)中如何優(yōu)雅得關(guān)閉協(xié)程方法
這篇文章主要為大家介紹了go語言開發(fā)中如何優(yōu)雅得關(guān)閉協(xié)程方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
詳解如何在golang鏡像中設(shè)置指定時(shí)區(qū)
這篇文章主要為大家詳細(xì)介紹了如何在golang鏡像中設(shè)置指定時(shí)區(qū),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的可以了解一下2023-04-04

