golang內(nèi)存對(duì)齊的概念及案例詳解
什么是內(nèi)存對(duì)齊
為保證程序順利高效的運(yùn)行,編譯器會(huì)把各種類型的數(shù)據(jù)安排到合適的地址,并占用合適的長度,這就是內(nèi)存對(duì)齊。
每種類型的對(duì)齊值就是它的對(duì)齊邊界,內(nèi)存對(duì)齊要求數(shù)據(jù)存儲(chǔ)地址以及占用的字節(jié)數(shù)都要是它的對(duì)齊邊界的倍數(shù)。所以下述的int32要錯(cuò)開兩個(gè)字節(jié),從4開始存,卻不能緊接著從2開始。
也可以這樣解釋:
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開始讀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)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
- 性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于,為了訪問未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對(duì)齊的內(nèi)存訪問僅需要一次訪問。
對(duì)齊邊界
那該怎么確定每種數(shù)據(jù)的對(duì)齊邊界呢?這和平臺(tái)有關(guān),go語言支持這些平臺(tái):
可以看到常見的32位平臺(tái),指針寬度和寄存器寬度都是4字節(jié),64位平臺(tái)上都是8字節(jié)。而被go語言稱為寄存器寬度的這個(gè)值,就可以理解為機(jī)器字長,也是平臺(tái)對(duì)應(yīng)的最大對(duì)齊邊界。
而數(shù)據(jù)類型的對(duì)齊邊界,是取類型大小與平臺(tái)最大對(duì)齊邊界中較小的那個(gè)。不過要注意,同一個(gè)類型在不同平臺(tái)上,大小可能不同,對(duì)齊邊界也可能不同。
為什么不統(tǒng)一使用平臺(tái)最大對(duì)齊邊界呢?或者統(tǒng)一按各類型大小來對(duì)齊呢?
我們來試一下,假設(shè)目前是64位平臺(tái),最大對(duì)齊邊界為8字節(jié)。int8只有1字節(jié),按照1字節(jié)對(duì)齊的話,它可以放在任何位置,因?yàn)榭偰芡ㄟ^一次讀取把它完整拿出來。如果統(tǒng)一對(duì)齊到8字節(jié),雖然同樣只要讀取一次,但每個(gè)int8的變量都要浪費(fèi)7字節(jié),所以對(duì)齊到1。
int16占2字節(jié),按照2字節(jié)對(duì)齊,可以從這些地址開始存,而且能保證只用讀取一次。
如果按1字節(jié)對(duì)齊就可能存成這樣,那就要讀取兩次再截取拼接,會(huì)影響性能。
如果按8字節(jié)對(duì)齊,會(huì)與int8一樣浪費(fèi)內(nèi)存,所以對(duì)齊到2。
這是小于最大對(duì)齊邊界的情況,再來看看大于的情況。
假設(shè)要在32位的平臺(tái)下存儲(chǔ)一個(gè)int64類型的數(shù)據(jù),在0和1位置被占用的情況下,就要從位置8開始存。而如果對(duì)齊到4,就可以從位置4開始,內(nèi)存浪費(fèi)更少,所以選擇對(duì)齊到4。
因此類型對(duì)齊邊界會(huì)這樣選擇,依然是為了減少浪費(fèi)提升性能。
GO 計(jì)算對(duì)齊邊界函數(shù)
在go語言中可以調(diào)用 unsafe.Alignof
來返回相應(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ì)齊邊界。
然后來存儲(chǔ)這個(gè)結(jié)構(gòu)體變量:
內(nèi)存對(duì)齊要求一:
- 存儲(chǔ)這個(gè)結(jié)構(gòu)體的起始地址,是對(duì)齊邊界的倍數(shù)。
?假設(shè)從0開始存,結(jié)構(gòu)體的每個(gè)成員在存儲(chǔ)時(shí),都要把這個(gè)起始地址當(dāng)作地址0,然后再用相對(duì)地址來決定自己該放在哪里。
內(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)讀者來思考。
參考資料:
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)文章
從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫初始化GORM
這篇文章主要為大家介紹了從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫初始化GORM,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01go語法入門泛型type?parameters簡稱T(類型形參)兩種場(chǎng)景使用
這篇文章主要為大家介紹了go語法入門泛型type?parameters簡稱T(類型形參)兩種場(chǎng)景使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09十個(gè)Golang開發(fā)中應(yīng)該避免的錯(cuò)誤總結(jié)
Go是一種靜態(tài)類型的、并發(fā)的、垃圾收集的編程語言,由谷歌開發(fā)。開發(fā)人員在編寫Go代碼時(shí)總會(huì)有一些常見的錯(cuò)誤,下面是Go語言中需要避免的十大壞錯(cuò)誤,希望對(duì)大家有所幫助2023-03-03Go+Vue開發(fā)一個(gè)線上外賣應(yīng)用的流程(用戶名密碼和圖形驗(yàn)證碼)
這篇文章主要介紹了Go+Vue開發(fā)一個(gè)線上外賣應(yīng)用(用戶名密碼和圖形驗(yàn)證碼),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11golang如何實(shí)現(xiàn)三元運(yùn)算符功能
這篇文章主要介紹了在其他一些編程語言中,如?C?語言,三元運(yùn)算符是一種可以用一行代碼實(shí)現(xiàn)條件選擇的簡便方法,那么在Go語言中如何實(shí)現(xiàn)類似功能呢,下面就跟隨小編一起學(xué)習(xí)一下吧2024-02-02