golang 內(nèi)存對(duì)齊的實(shí)現(xiàn)
什么是內(nèi)存對(duì)齊
在訪問(wèn)特定類型變量的時(shí)候通常在特定的內(nèi)存地址訪問(wèn),這就需要對(duì)這些數(shù)據(jù)在內(nèi)存中存放的位置有限制,各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。
內(nèi)存對(duì)齊是編譯器的管轄范圍。表現(xiàn)為:編譯器為程序中的每個(gè)“數(shù)據(jù)單元”安排在適當(dāng)?shù)奈恢蒙稀?/p>
為什么需要內(nèi)存對(duì)齊
有些CPU可以訪問(wèn)任意地址上的任意數(shù)據(jù),而有些CPU只能在特定地址訪問(wèn)數(shù)據(jù),因此不同硬件平臺(tái)具有差異性,這樣的代碼就不具有移植性,如果在編譯時(shí),將分配的內(nèi)存進(jìn)行對(duì)齊,這就具有平臺(tái)可以移植性了。
CPU 訪問(wèn)內(nèi)存時(shí)并不是逐個(gè)字節(jié)訪問(wèn),而是以字長(zhǎng)(word size)為單位訪問(wèn),例如 32位的CPU 字長(zhǎng)是4字節(jié),64位的是8字節(jié)。如果變量的地址沒(méi)有對(duì)齊,可能需要多次訪問(wèn)才能完整讀取到變量?jī)?nèi)容,而對(duì)齊后可能就只需要一次內(nèi)存訪問(wèn),因此內(nèi)存對(duì)齊可以減少CPU訪問(wèn)內(nèi)存的次數(shù),加大CPU訪問(wèn)內(nèi)存的吞吐量。
假設(shè)每次訪問(wèn)的步長(zhǎng)為4個(gè)字節(jié),如果未經(jīng)過(guò)內(nèi)存對(duì)齊,獲取b的數(shù)據(jù)需要進(jìn)行兩次內(nèi)存訪問(wèn),最后再進(jìn)行數(shù)據(jù)整理得到b的完整數(shù)據(jù):
如果經(jīng)過(guò)內(nèi)存對(duì)齊,一次內(nèi)存訪問(wèn)就能得到b的完整數(shù)據(jù),減少了一次內(nèi)存訪問(wèn):
golang中unsafe.AlignOf()函數(shù)
unsafe.AlignOf(x) 方法的返回值是 m,當(dāng)變量進(jìn)行內(nèi)存對(duì)齊時(shí),需要保證分配到 x 的內(nèi)存地址能夠整除 m。因此可以通過(guò)這個(gè)方法,確定變量x 在內(nèi)存對(duì)齊時(shí)的地址:
- 對(duì)于任意類型的變量 x ,unsafe.Alignof(x) 至少為 1。
- 對(duì)于 struct 結(jié)構(gòu)體類型的變量 x,計(jì)算 x 每一個(gè)字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。
- 對(duì)于 array 數(shù)組類型的變量x,unsafe.Alignof(x) 等于構(gòu)成數(shù)組的元素類型的對(duì)齊倍數(shù)。
對(duì)于系統(tǒng)內(nèi)置基礎(chǔ)類型變量 x ,unsafe.Alignof(x) 的返回值就是 min(字長(zhǎng)/8,unsafe.Sizeof(x)),即計(jì)算機(jī)字長(zhǎng)與類型占用內(nèi)存的較小值:
func main() { fmt.Println(unsafe.Alignof(int(1))) // 1 -- min(8,1) fmt.Println(unsafe.Alignof(int32(1))) // 4 -- min (8,4) fmt.Println(unsafe.Alignof(int64(1))) // 8 -- min (8,8) fmt.Println(unsafe.Alignof(complex128(1))) // 8 -- min(8,16) }
內(nèi)存對(duì)齊規(guī)則
- 成員對(duì)齊規(guī)則
針對(duì)一個(gè)基礎(chǔ)類型變量,如果 unsafe.AlignOf() 返回的值是 m,那么該變量的地址需要 被m整除 (如果當(dāng)前地址不能整除,填充空白字節(jié),直至可以整除)。
- 整體對(duì)齊規(guī)則
針對(duì)一個(gè)結(jié)構(gòu)體,如果 unsafe.AlignOf() 返回值是 m,需要保證該結(jié)構(gòu)體整體內(nèi)存占用是 m的整數(shù)倍,如果當(dāng)前不是整數(shù)倍,需要在后面填充空白字節(jié)。
通過(guò)內(nèi)存對(duì)齊后,就可以在保證在訪問(wèn)一個(gè)變量地址時(shí):
- 如果該變量占用內(nèi)存小于字長(zhǎng):保證一次訪問(wèn)就能得到數(shù)據(jù);
- 如果該變量占用內(nèi)存大于字長(zhǎng):保證第一次內(nèi)存訪問(wèn)的首地址,是該變量的首地址。
eg:
type A struct { a int32 b int64 c int32 } func main() { fmt.Println(unsafe.Sizeof(A{1, 1, 1})) // 24 }
第一個(gè)字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內(nèi)存占用為4個(gè)字節(jié),同時(shí)unsafe.Alignof(int32(1)) = 4,內(nèi)存對(duì)齊需保證變量首地址可以被4整除,我們假設(shè)地址從0開(kāi)始,0可以被4整除:
2. 第二個(gè)字段是 int64 類型,unsafe.Sizeof(int64(1)) = 8,內(nèi)存占用為 8 個(gè)字節(jié),同unsafe.Alignof(int64(1)) = 8,需保證變量放置首地址可以被8整除,當(dāng)前地址為4,距離4最近的且可以被8整除的地址為8,因此需要添加四個(gè)空白字節(jié),從8開(kāi)始放置:
第三個(gè)字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內(nèi)存占用為4個(gè)字節(jié),同時(shí)unsafe.Alignof(int32(1)) = 4,內(nèi)存對(duì)齊需保證變量首地址可以被4整除,當(dāng)前地址為16,16可以被4整除:
所有成員對(duì)齊都已經(jīng)完成,現(xiàn)在我們需要看一下整體對(duì)齊規(guī)則:unsafe.Alignof(A{}) = 8,即三個(gè)變量成員的最大值,內(nèi)存對(duì)齊需要保證該結(jié)構(gòu)體的內(nèi)存占用是 8 的整數(shù)倍,當(dāng)前內(nèi)存占用是 20個(gè)字節(jié),因此需要再補(bǔ)充4個(gè)字節(jié):
最終該結(jié)構(gòu)體的內(nèi)存占用為 24字節(jié)。
type B struct { a int32 b int32 c int64 } func main() { fmt.Println(unsafe.Sizeof(B{1, 1, 1})) // 16 }
第一個(gè)字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內(nèi)存占用為4個(gè)字節(jié),同時(shí)unsafe.Alignof(int32(1)) = 4,內(nèi)存對(duì)齊需保證變量首地址可以被4整除,我們假設(shè)地址從0開(kāi)始,0可以被4整除:
第二個(gè)字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內(nèi)存占用為4個(gè)字節(jié),同時(shí)unsafe.Alignof(int32(1)) = 4,內(nèi)存對(duì)齊需保證變量首地址可以被4整除,當(dāng)前地址為4,4可以被4整除:
第三個(gè)字段是 int64 類型,unsafe.Sizeof(int64(1))=8,內(nèi)存占用為8個(gè)字節(jié),同時(shí)unsafe.Alignof(int64(1)) = 8,內(nèi)存對(duì)齊需保證變量首地址可以被8整除,當(dāng)前地址為8,8可以被8整除:
所有成員對(duì)齊都已經(jīng)完成,現(xiàn)在我們需要看一下整體對(duì)齊規(guī)則:unsafe.Alignof(B{}) = 8,即三個(gè)變量成員的最大值,內(nèi)存對(duì)齊需要保證該結(jié)構(gòu)體的內(nèi)存占用是 8 的整數(shù)倍,當(dāng)前內(nèi)存占用是 16個(gè)字節(jié),已經(jīng)符合規(guī)則,最終該結(jié)構(gòu)體的內(nèi)存占用為 16個(gè)字節(jié)。
空結(jié)構(gòu)體對(duì)齊規(guī)則
如果空結(jié)構(gòu)體作為結(jié)構(gòu)體的內(nèi)置字段:當(dāng)變量位于結(jié)構(gòu)體的前面和中間時(shí),不會(huì)占用內(nèi)存;當(dāng)該變量位于結(jié)構(gòu)體的末尾位置時(shí),需要進(jìn)行內(nèi)存對(duì)齊,內(nèi)存占用大小和前一個(gè)變量的大小保持一致。
type C struct { a struct{} b int64 c int64 } type D struct { a int64 b struct{} c int64 } type E struct { a int64 b int64 c struct{} } type F struct { a int32 b int32 c struct{} } func main() { fmt.Println(unsafe.Sizeof(C{})) // 16 fmt.Println(unsafe.Sizeof(D{})) // 16 fmt.Println(unsafe.Sizeof(E{})) // 24 fmt.Println(unsafe.Sizeof(F{})) // 12 }
參考:https://juejin.cn/post/7077833959047954463
到此這篇關(guān)于golang 內(nèi)存對(duì)齊的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)golang 內(nèi)存對(duì)齊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語(yǔ)言import報(bào)錯(cuò)處理圖文詳解
今天本來(lái)想嘗試一下go語(yǔ)言中公有和私有的方法,結(jié)果import其他包的時(shí)候直接報(bào)錯(cuò)了,下面這篇文章主要給大家介紹了關(guān)于go語(yǔ)言import報(bào)錯(cuò)處理的相關(guān)資料,需要的朋友可以參考下2023-04-04基于Go+WebSocket實(shí)現(xiàn)實(shí)時(shí)通信功能
在互聯(lián)網(wǎng)應(yīng)用程序中,實(shí)時(shí)通信是一種非常重要的功能,WebSocket 是一種基于 TCP 的協(xié)議,它允許客戶端和服務(wù)器之間進(jìn)行雙向通信,本文將介紹如何使用 Golang 創(chuàng)建單獨(dú)的 WebSocket 會(huì)話,以實(shí)現(xiàn)實(shí)時(shí)通信功能,需要的朋友可以參考下2023-10-10Go語(yǔ)言學(xué)習(xí)之goroutine詳解
Goroutine是建立在線程之上的輕量級(jí)的抽象。它允許我們以非常低的代價(jià)在同一個(gè)地址空間中并行地執(zhí)行多個(gè)函數(shù)或者方法,這篇文章主要介紹了Go語(yǔ)言學(xué)習(xí)之goroutine的相關(guān)知識(shí),需要的朋友可以參考下2020-02-02Go語(yǔ)言加解密利器之go-crypto庫(kù)用法解析
在軟件開(kāi)發(fā)中,數(shù)據(jù)安全和隱私保護(hù)越來(lái)越受到重視,go-crypto?庫(kù)應(yīng)運(yùn)而生,它是一個(gè)專為?Golang?設(shè)計(jì)的加密解密工具庫(kù),下面就跟隨小編一起來(lái)看看它的具體使用吧2024-11-11詳解在Go語(yǔ)言單元測(cè)試中如何解決文件依賴問(wèn)題
現(xiàn)如今的?Web?應(yīng)用程序往往采用?RESTful?API?接口形式對(duì)外提供服務(wù),后端接口直接向前端返回?HTML?文件的情況越來(lái)越少,所以在程序中操作文件的場(chǎng)景也變少了,在編寫(xiě)單元測(cè)試時(shí),文件就成了被測(cè)試代碼的外部依賴,本文就來(lái)講解下測(cè)試過(guò)程中如何解決文件外部依賴問(wèn)題2023-08-08Go結(jié)合反射將結(jié)構(gòu)體轉(zhuǎn)換成Excel的過(guò)程詳解
這篇文章主要介紹了Go結(jié)合反射將結(jié)構(gòu)體轉(zhuǎn)換成Excel的過(guò)程詳解,大概思路是在Go的結(jié)構(gòu)體中每個(gè)屬性打上一個(gè)excel標(biāo)簽,利用反射獲取標(biāo)簽中的內(nèi)容,作為表格的Header,需要的朋友可以參考下2022-06-06