Golang內(nèi)存對(duì)齊的規(guī)則及實(shí)現(xiàn)
什么是內(nèi)存對(duì)齊?
編譯器會(huì)將數(shù)據(jù)按照特定的規(guī)則,把數(shù)據(jù)安排到合適的存儲(chǔ)地址上,并占用合適的地址長度
為什么要內(nèi)存對(duì)齊
保證程序順利高效的運(yùn)行,可以讓CPU快速從內(nèi)存中存取到字段,避免資源浪費(fèi)
內(nèi)存對(duì)齊規(guī)則
1、起始的存儲(chǔ)地址 必須是 內(nèi)存對(duì)齊邊界 的倍數(shù)。
2、整體占用字節(jié)數(shù) 必須是 內(nèi)存對(duì)齊邊界 的倍數(shù)。
Tip:先聲明兩個(gè)概念 ↓ ↓
- 內(nèi)存對(duì)齊邊界:結(jié)構(gòu)體所有元素中,哪個(gè)元素占用的字節(jié)數(shù)大,那么這個(gè)元素占用的字節(jié)數(shù)就是內(nèi)存對(duì)齊邊界
- 對(duì)齊邊界:結(jié)構(gòu)體中每個(gè)元素自己占用的字節(jié)數(shù)
通過下邊的示例來理解內(nèi)存對(duì)齊的規(guī)則
首先我們定義一個(gè)結(jié)構(gòu)體:
type S struct { A uint8 // byte:1 B int32 // byte:4 C int16 // byte:2 D int64 // byte:8 E [2]string // byte總和:32 -->string類型數(shù)組,總共2個(gè)元素,每個(gè)元素包含2部分內(nèi)容: // 內(nèi)容1:ptr,指向存放數(shù)據(jù)的地址,byte:8: 內(nèi)容2:len,標(biāo)識(shí)字符串長度的整數(shù)值,byte:8 F struct{} // zero size field }
步驟一:確定內(nèi)存對(duì)齊邊界
統(tǒng)計(jì)出結(jié)構(gòu)體中所有的元素分別占用的字節(jié)數(shù),占用最大的字節(jié)數(shù)就是內(nèi)存對(duì)齊邊界,在這個(gè)示例中的內(nèi)存對(duì)齊邊界就是 8 Tip:如果不知道怎樣確定內(nèi)存對(duì)齊邊界,可以使用unsafe.Alignof()函數(shù)打印每個(gè)元素的對(duì)齊系數(shù),打印中的最大值就是內(nèi)存對(duì)齊邊界:
func main() { fmt.Println(unsafe.Alignof(S{}.A)) // output: 1 fmt.Println(unsafe.Alignof(S{}.B)) // output: 4 fmt.Println(unsafe.Alignof(S{}.C)) // output: 2 fmt.Println(unsafe.Alignof(S{}.D)) // output: 8 fmt.Println(unsafe.Alignof(S{}.E)) // output: 8 fmt.Println(unsafe.Alignof(S{}.F)) // output: 1 fmt.Println(unsafe.Sizeof(S{})) //可以直接打印出結(jié)構(gòu)體所占用的字節(jié)數(shù) }
步驟二:確定起始存儲(chǔ)地址
內(nèi)存對(duì)齊規(guī)則第一條:起始的存儲(chǔ)地址 必須是 內(nèi)存對(duì)齊邊界 的倍數(shù)。也就是(起始地址addr)%(內(nèi)存對(duì)齊邊界)=0,在我們示例中起始地址就是addr%8=0,我們從0地址開始,為了方便看,我們先用圖來展示地址存儲(chǔ)分布圖
起始地址為0,0%8=0,符合條件,那我們就從0開始存儲(chǔ)數(shù)據(jù)
其中元素A占用1個(gè)字節(jié),并且0%1=0,符合條件,第0個(gè)地址分配給A;
元素B占用4個(gè)字節(jié),從1個(gè)地址分配的話,1%4≠0,只有4%4=0,所以把第4-7個(gè)地址分配給B;
以此類推······ 具體分配如下
元素A:1個(gè)字節(jié),占用第0個(gè)相對(duì)地址空間, 0%1 =0(起始地址為0,內(nèi)存邊界為1)
元素B:4個(gè)字節(jié),占用第4-7個(gè)相對(duì)地址空間, 4%4 =0(起始地址為4,內(nèi)存邊界為4)
元素C:2個(gè)字節(jié),占用第8-9個(gè)相對(duì)地址空間, 8%2 =0(起始地址為8,內(nèi)存邊界為2)
元素D:8個(gè)字節(jié),占用第16-23個(gè)相對(duì)地址空間, 16%8=0(起始地址為16,內(nèi)存邊界為8)
元素E:8*2*2個(gè)字節(jié),占用第24-31和32-39和40-47和48-55個(gè)相對(duì)地址空間,24%8=0...(起始地址為24,內(nèi)存邊界為8...)
元素F:zero size field,占用第56個(gè)相對(duì)地址空間, 56%1=0 (起始地址為56,內(nèi)存邊界為1)
最終內(nèi)存在第56個(gè)相對(duì)地址空間分配完畢,但是我們的地址空間是從0開始計(jì)算的,所以目前來看結(jié)構(gòu)體總共占用57個(gè)字節(jié)!
步驟三:確定結(jié)構(gòu)體占用字節(jié)數(shù)
我們?cè)诓襟E二中排列出了結(jié)構(gòu)體中元素的存放地址,整體元素占用57個(gè)字節(jié),但是到這里還不算完事兒,因?yàn)槲覀冞€沒有執(zhí)行第二條的內(nèi)存對(duì)齊規(guī)則–>整體占用字節(jié)數(shù) 必須是 內(nèi)存對(duì)齊邊界 的倍數(shù)。我們的內(nèi)存對(duì)齊邊界為8,而57不是8的倍數(shù),所以我們需要擴(kuò)張字節(jié)空間到8的倍數(shù),延伸到64,也就是擴(kuò)張到到圖中的第63個(gè)相對(duì)地址空間。這個(gè)結(jié)構(gòu)體占用的字節(jié)數(shù)為64字節(jié)!
內(nèi)存空間優(yōu)化
通過上邊的示例與圖表我們不難看出,其中還有好多個(gè)地址空間被浪費(fèi)掉了,這些沒被利用的地址空間,go語言會(huì)進(jìn)行padding操作來對(duì)這些空間進(jìn)行填充,使這些空間變成合法的內(nèi)存空間。
我們?cè)偎伎家幌?,如何才能減少地址空間的浪費(fèi)呢?能不能通過重新排列元素的位置來合理的分配地址空間呢?答案當(dāng)然是肯定的,我們可以通過合理排列元素的定義順序來減少地址空間的浪費(fèi)。
我們先看結(jié)論,下邊是重新排列后的結(jié)構(gòu)體:
type S1 struct { A uint8 F struct{} C int16 B int32 D int64 E [2]string }
再看一下重新分配地址空間的圖標(biāo):
起始地址為0,0%8=0,符合條件,我們就從0開始存儲(chǔ)數(shù)據(jù)
其中元素A占用1個(gè)字節(jié),并且0%1=0,符合條件,第0個(gè)地址分配給A; 元素F占用1個(gè)字節(jié),1%1=0,符合條件,將第1個(gè)地址分配給B; 以此類推······ 具體分配如下
元素A:1個(gè)字節(jié),占用第0個(gè)相對(duì)地址空間, 0%1 =0(起始地址為0,內(nèi)存邊界為1)
元素F:zero size field,占用第1個(gè)相對(duì)地址空間,1%1 =0(起始地址為1,內(nèi)存邊界為1)
元素C:2個(gè)字節(jié),占用第2-3個(gè)相對(duì)地址空間, 2%2 =0(起始地址為2,內(nèi)存邊界為2)
元素B:4個(gè)字節(jié),占用第4-7個(gè)相對(duì)地址空間, 4%4=0(起始地址為4,內(nèi)存邊界為4)
元素D:8個(gè)字節(jié),占用第8-15個(gè)相對(duì)地址空間, 8%8=0(起始地址為8,內(nèi)存邊界為8)
元素E:8*2*2個(gè)字節(jié),占用第16-23和24-31和32-39和40-47個(gè)相對(duì)地址空間,16%8=0... (起始地址為16,內(nèi)存邊界為8...)
最終內(nèi)存在第47個(gè)相對(duì)地址空間分配完畢,但是我們的地址空間是從0開始計(jì)算的,所以目前來看結(jié)構(gòu)體總共占用48個(gè)字節(jié)!并且48還是內(nèi)存對(duì)齊邊界值8的整數(shù)倍,所以結(jié)構(gòu)體最終占用48個(gè)字節(jié)!
我們可以很明顯的看出來,在我們改變?cè)氐亩x順序后,占用的字節(jié)空間從64字節(jié)減少到了48字節(jié),內(nèi)存空間得到了充分的優(yōu)化?。?!這也是一個(gè)結(jié)論所在,我們?cè)诮Y(jié)構(gòu)體定義變量的時(shí)候,盡量將相同類型的變量定義在一起,將占用字節(jié)較少的變量類型放在一塊。
到此這篇關(guān)于Golang內(nèi)存對(duì)齊的規(guī)則及實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang內(nèi)存對(duì)齊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言異常處理(Panic和recovering)用法詳解
異常處理是程序健壯性的關(guān)鍵,往往開發(fā)人員的開發(fā)經(jīng)驗(yàn)的多少從異常部分處理上就能得到體現(xiàn)。Go語言中沒有Try?Catch?Exception機(jī)制,但是提供了panic-and-recover機(jī)制,本文就來詳細(xì)講講他們的用法2022-07-07Goland使用Go Modules創(chuàng)建/管理項(xiàng)目的操作
這篇文章主要介紹了Goland使用Go Modules創(chuàng)建/管理項(xiàng)目的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05Golang并發(fā)發(fā)送HTTP請(qǐng)求的各種方法
在 Golang 領(lǐng)域,并發(fā)發(fā)送 HTTP 請(qǐng)求是優(yōu)化 Web 應(yīng)用程序的一項(xiàng)重要技能,本文探討了實(shí)現(xiàn)此目的的各種方法,從基本的 goroutine 到涉及通道和sync.WaitGroup 的高級(jí)技術(shù),需要的朋友可以參考下2024-02-02golang中import cycle not allowed解決的一種思路
這篇文章主要給大家介紹了關(guān)于golang中import cycle not allowed解決的一種思路,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08go語言實(shí)現(xiàn)將重要數(shù)據(jù)寫入圖片中
本文給大家分享的是go語言實(shí)現(xiàn)將數(shù)據(jù)的二進(jìn)制形式寫入圖像紅色通道數(shù)據(jù)二進(jìn)制的低位,從而實(shí)現(xiàn)將重要數(shù)據(jù)隱藏,有需要的小伙伴參考下吧。2015-03-03golang 兩個(gè)go程輪流打印一個(gè)切片的實(shí)現(xiàn)
這篇文章主要介紹了golang 兩個(gè)go程輪流打印一個(gè)切片的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10Windows上安裝Go并配置環(huán)境變量(圖文步驟)
開始使用Go創(chuàng)建應(yīng)用程序之前,需要設(shè)置開發(fā)環(huán)境,本文主要介紹了Windows上安裝Go并配置環(huán)境變量,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08