golang內(nèi)存對(duì)齊的概念及案例詳解
什么是內(nèi)存對(duì)齊
為保證程序順利高效的運(yùn)行,編譯器會(huì)把各種類型的數(shù)據(jù)安排到合適的地址,并占用合適的長(zhǎng)度,這就是內(nèi)存對(duì)齊。
每種類型的對(duì)齊值就是它的對(duì)齊邊界,內(nèi)存對(duì)齊要求數(shù)據(jù)存儲(chǔ)地址以及占用的字節(jié)數(shù)都要是它的對(duì)齊邊界的倍數(shù)。所以下述的int32要錯(cuò)開(kāi)兩個(gè)字節(jié),從4開(kāi)始存,卻不能緊接著從2開(kāi)始。

也可以這樣解釋:
CPU把內(nèi)存當(dāng)成是一塊一塊的,塊的大小可以是2,4,8,16字節(jié)大小,因此CPU在讀取內(nèi)存時(shí)是一塊一塊進(jìn)行讀取的。塊大小成為memory access granularity(粒度)。
如果不進(jìn)行內(nèi)存對(duì)齊
比如我們想從地址1開(kāi)始讀8字節(jié)的數(shù)據(jù):

CPU會(huì)分兩次讀:
- 第一次從
0 - 7但只取后7字節(jié)。 - 第二次從
8 - 15但只取第1字節(jié)。
分兩次讀,這樣勢(shì)必會(huì)對(duì)性能造成影響。
為什么要內(nèi)存對(duì)齊
原因主要有兩點(diǎn):
- 平臺(tái)原因(移植原因):不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
- 性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于,為了訪問(wèn)未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問(wèn);而對(duì)齊的內(nèi)存訪問(wèn)僅需要一次訪問(wèn)。
對(duì)齊邊界
那該怎么確定每種數(shù)據(jù)的對(duì)齊邊界呢?這和平臺(tái)有關(guān),go語(yǔ)言支持這些平臺(tái):
可以看到常見(jiàn)的32位平臺(tái),指針寬度和寄存器寬度都是4字節(jié),64位平臺(tái)上都是8字節(jié)。而被go語(yǔ)言稱為寄存器寬度的這個(gè)值,就可以理解為機(jī)器字長(zhǎng),也是平臺(tái)對(duì)應(yīng)的最大對(duì)齊邊界。

而數(shù)據(jù)類型的對(duì)齊邊界,是取類型大小與平臺(tái)最大對(duì)齊邊界中較小的那個(gè)。不過(guò)要注意,同一個(gè)類型在不同平臺(tái)上,大小可能不同,對(duì)齊邊界也可能不同。

為什么不統(tǒng)一使用平臺(tái)最大對(duì)齊邊界呢?或者統(tǒng)一按各類型大小來(lái)對(duì)齊呢?
我們來(lái)試一下,假設(shè)目前是64位平臺(tái),最大對(duì)齊邊界為8字節(jié)。int8只有1字節(jié),按照1字節(jié)對(duì)齊的話,它可以放在任何位置,因?yàn)榭偰芡ㄟ^(guò)一次讀取把它完整拿出來(lái)。如果統(tǒng)一對(duì)齊到8字節(jié),雖然同樣只要讀取一次,但每個(gè)int8的變量都要浪費(fèi)7字節(jié),所以對(duì)齊到1。

int16占2字節(jié),按照2字節(jié)對(duì)齊,可以從這些地址開(kāi)始存,而且能保證只用讀取一次。

如果按1字節(jié)對(duì)齊就可能存成這樣,那就要讀取兩次再截取拼接,會(huì)影響性能。

如果按8字節(jié)對(duì)齊,會(huì)與int8一樣浪費(fèi)內(nèi)存,所以對(duì)齊到2。
這是小于最大對(duì)齊邊界的情況,再來(lái)看看大于的情況。
假設(shè)要在32位的平臺(tái)下存儲(chǔ)一個(gè)int64類型的數(shù)據(jù),在0和1位置被占用的情況下,就要從位置8開(kāi)始存。而如果對(duì)齊到4,就可以從位置4開(kāi)始,內(nèi)存浪費(fèi)更少,所以選擇對(duì)齊到4。

因此類型對(duì)齊邊界會(huì)這樣選擇,依然是為了減少浪費(fèi)提升性能。

GO 計(jì)算對(duì)齊邊界函數(shù)
在go語(yǔ)言中可以調(diào)用 unsafe.Alignof 來(lái)返回相應(yīng)類型的對(duì)齊邊界:
func main() {
fmt.Printf("bool align: %d\n", unsafe.Alignof(bool(true)))
fmt.Printf("int32 align: %d\n", unsafe.Alignof(int32(0)))
fmt.Printf("int8 align: %d\n", unsafe.Alignof(int8(0)))
fmt.Printf("int64 align: %d\n", unsafe.Alignof(int64(0)))
fmt.Printf("byte align: %d\n", unsafe.Alignof(byte(0)))
fmt.Printf("string align: %d\n", unsafe.Alignof("EDDYCJY"))
fmt.Printf("map align: %d\n", unsafe.Alignof(map[string]string{}))
}運(yùn)行結(jié)果:
bool align: 1
int32 align: 4
int8 align: 1
int64 align: 8
byte align: 1
string align: 8
map align: 8
確定結(jié)構(gòu)體的對(duì)齊邊界
對(duì)結(jié)構(gòu)體而言,首先要確定每個(gè)成員的對(duì)齊邊界,然后取其中最大的,這就是這個(gè)結(jié)構(gòu)體的對(duì)齊邊界。

然后來(lái)存儲(chǔ)這個(gè)結(jié)構(gòu)體變量:
內(nèi)存對(duì)齊要求一:
- 存儲(chǔ)這個(gè)結(jié)構(gòu)體的起始地址,是對(duì)齊邊界的倍數(shù)。
?假設(shè)從0開(kāi)始存,結(jié)構(gòu)體的每個(gè)成員在存儲(chǔ)時(shí),都要把這個(gè)起始地址當(dāng)作地址0,然后再用相對(duì)地址來(lái)決定自己該放在哪里。
內(nèi)存對(duì)齊要求2:
- 結(jié)構(gòu)體整體占用字節(jié)數(shù)需要是類型對(duì)齊邊界的倍數(shù),不夠的話要往后擴(kuò)張一下。
?所以最終上述結(jié)構(gòu)體類型的大小就是24字節(jié)。
案例
type Part1 struct {
a bool
b int32
c int8
d int64
e byte
}type Part2 struct {
a bool
c int8
e byte
b int32 // 4個(gè)字節(jié)
d int64
}分別求以上兩個(gè)結(jié)構(gòu)體占用的字節(jié):
fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2))這里我們直接調(diào)用函數(shù)求得:
part1 size: 32, align: 8 part2 size: 16, align: 8
原因請(qǐng)讀者來(lái)思考。
參考資料:
https://blog.csdn.net/u010853261/article/details/102557188
https://www.bilibili.com/video/BV1Ja4y1i7AF?from=search&seid=16213689667007976568&spm_id_from=333.337.0.0
到此這篇關(guān)于golang內(nèi)存對(duì)齊的文章就介紹到這了,更多相關(guān)golang內(nèi)存對(duì)齊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang結(jié)構(gòu)化日志log/slog包之LogValuer的用法簡(jiǎn)介
這篇文章主要為大家詳細(xì)介紹了golang結(jié)構(gòu)化日志log/slog包中 LogValuer 和日志記錄函數(shù)的正確包裝方法,感興趣的小伙伴可以跟隨小編一起了解一下2023-10-10
Go語(yǔ)言開(kāi)發(fā)redis封裝及簡(jiǎn)單使用詳解
這篇文章主要為大家介紹了Go語(yǔ)言開(kāi)發(fā)redis的封裝及簡(jiǎn)單使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-11-11
golang通過(guò)反射設(shè)置結(jié)構(gòu)體變量的值
這篇文章主要介紹了golang通過(guò)反射設(shè)置結(jié)構(gòu)體變量的值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Golang中Map按照Value大小排序的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Golang中Map按照Value大小排序的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03
go語(yǔ)言LeetCode題解1030距離順序排列矩陣單元格
這篇文章主要為大家介紹了go語(yǔ)言LeetCode題解1030距離順序排列矩陣單元格,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Go語(yǔ)言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用
設(shè)計(jì)模式是軟件工程中各種常見(jiàn)問(wèn)題的經(jīng)典解決方案,,本文主要介紹了Go語(yǔ)言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Golang?HTTP服務(wù)超時(shí)控制實(shí)現(xiàn)原理分析
這篇文章主要介紹了Golang?HTTP服務(wù)超時(shí)控制實(shí)現(xiàn)原理,HTTP服務(wù)的超時(shí)控制是保障服務(wù)高可用性的重要措施之一,由于HTTP服務(wù)可能會(huì)遇到網(wǎng)絡(luò)延遲,資源瓶頸等問(wèn)題,因此需要對(duì)請(qǐng)求進(jìn)行超時(shí)控制,以避免服務(wù)雪崩等問(wèn)題,需要的朋友可以參考下2023-05-05

