GO 記憶體對齊
前言
之前遇到過這樣一個情況(發現問題的結構體並不長這樣, 不過為了引出問題, 改了一下):
type Test struct { | |
b bool | |
i3 int32 | |
i8 int8 | |
i64 int64 | |
by byte | |
} | |
func main() { | |
t := Test{} | |
fmt.Printf("%d", unsafe.Sizeof(t)) | |
} |
建立一個結構體, 檢視一下其記憶體佔用. 看結果前先簡單算一下:
bool
: 1Bint32
: 4Bint8
: 1Bint64
: 8Bbyte
: 1B
這麼算下來的話, Test
結構體佔用應該是: 1+4+1+8+1=15B
. 15個位元組對吧. 來, 列印看一下:
32個位元組???這不坑我麼.記憶體佔用直接多出一倍.
探索
通過查詢資料, 發現了這樣一個名詞: 記憶體對齊
. 什麼是記憶體對齊
呢?
簡單說, 就是CPU
在讀取資料的時候, 並不是一個位元組一個位元組讀取的, 而是一塊一塊讀取的. 那麼這個快是多大呢? 根據CPU
位數不同而不同.
而GO
編譯器在編譯的時候, 為了保證記憶體對齊, 對每一個資料型別都給出了對齊保證, 將未對齊的記憶體留空. 如果一個型別的對齊保證是4B, 那麼其資料存放的起始地址偏移量必是4B 的整數倍. 而編譯器給出的這個對齊保證是多少呢? 不同版本不同平臺的編譯器不盡相同, 可以通過函式unsafe.Alignof
來獲取.
通過分析之前的資料結果, 就能大致理解了. 先來看一下幾個型別對齊保證的值:
fmt.Printf("bool: %d\n", unsafe.Alignof(bool(false))) | |
fmt.Printf("int32: %d\n", unsafe.Alignof(int32(0))) | |
fmt.Printf("int8: %d\n", unsafe.Alignof(int8(0))) | |
fmt.Printf("int64: %d\n", unsafe.Alignof(int64(0))) | |
fmt.Printf("byte: %d\n", unsafe.Alignof(byte(0))) |
結果如下:
來嘗試一個一個放到記憶體中(下圖中每個空白代表一個位元組):
1.放入bool
: 其對齊保證為1, 第一個變數, 直接放入即可.
2.放入int32
. 其對齊保證為4, 既偏移量為4的整數倍. 而現有地址中, 首個4的整數倍為第四個位元組(中間三位元組留空).
按照這個思路, 依次將後面的變數放入, 結果佔用的記憶體為(其中字母依次為變數佔用, X
為對齊留空):
AXXX BBBB CXXX XXXX DDDD DDDD E
但是這才25
個位元組啊. 和實際的32位元組還差點呢. 別急, 再看一下結構體的對齊保證, 發現是8B. 上面不是8B 的整數倍, 往後補零. 結果:
AXXX BBBB CXXX XXXX DDDD DDDD EXXX XXXX
如此一來, 就正好32位了. 結構體的對齊保證, 為其成員變數對齊保證的最大值.
why
那麼編譯器為什麼要做記憶體對齊這種事情呢? 舉個例子, 如果不做記憶體對齊, 那麼下面這個結構體的記憶體分佈為:
type Test struct { | |
b bool | |
i3 int32 | |
} |
ABBB B
還記得之前說, CPU
讀取記憶體是一塊一塊讀取的麼? 而這個塊, 假設是4B
.
這樣的話, 當你需要讀取i3
變數的時候, 需要進行兩次記憶體訪問. 而對齊之後, 只需要進行一次記憶體訪問即可. 是典型的空間換時間的做法.
修改
既然知道了問題出在哪裡, 那麼是不是如果換一下欄位的存放順序, 就可以壓縮記憶體空間了呢? 思路很簡單, 將對齊保證小的放到前面, 試一下:
type Test struct { | |
b bool | |
by byte | |
i8 int8 | |
i3 int32 | |
i64 int64 | |
} | |
func main() { | |
t := Test{} | |
fmt.Printf("%d", unsafe.Sizeof(t)) | |
} |
通過之前的對齊分析. 結果確為18B. 也就是因為欄位順序的問題, 編譯器為了保證記憶體對齊, 向其中填充了很多空白, 造成了記憶體的浪費.
僅僅是修改了一下欄位的順序, 就可以將結構體的記憶體佔用直接降低一倍. 見識了...
檢測工具
那麼, 有沒有什麼辦法能夠幫我們檢測是否存在記憶體對齊的優化呢? 畢竟平常寫的時候, 誰會關心這玩意呢. 別說, 還真有. golangci-lint
官網: https://golangci-lint.run/
安裝: brew install golangci-lint
檢測所有檔案命令: golangci-lint run ./..
檢測一下最開始的結構體檔案(新增引數指定檢測記憶體對齊):
golangci-lint run --disable-all -E maligned main.go
看到結果:
會看到提示, 該結構體當前佔有32B, 可優化至16B. 完美.
當然, 此工具的功能不僅如此, 它能夠提供很多建議, 有待發掘.
其實, 專案中估計也很少有關注記憶體對齊的時候吧. 不過畢竟積少成多, 記憶體這玩意, 能省則省嘛.
相關文章
- 記憶體對齊記憶體
- 理解記憶體對齊記憶體
- 結構體記憶體對齊結構體記憶體
- C# 記憶體對齊C#記憶體
- Dig101:Go 之聊聊 struct 的記憶體對齊GoStruct記憶體
- 從 CPU 角度理解 Go 中的結構體記憶體對齊Go結構體記憶體
- Go plan9 彙編:記憶體對齊和遞迴Go記憶體遞迴
- iOS 記憶體位元組對齊iOS記憶體
- C語言記憶體對齊C語言記憶體
- Go高效能程式設計-瞭解記憶體對齊以及Go中的型別如何對齊保證Go程式設計記憶體型別
- C++ struct結構體記憶體對齊C++Struct結構體記憶體
- c 結構體記憶體對齊詳解結構體記憶體
- iOS探索 記憶體對齊&malloc原始碼iOS記憶體原始碼
- C/C++記憶體對齊原則C++記憶體
- C/C++記憶體對齊詳解C++記憶體
- Go 的記憶體對齊和指標運算詳解和實踐Go記憶體指標
- Netty原始碼解析 -- 記憶體對齊類SizeClassesNetty原始碼記憶體
- struct結構體大小的計算(記憶體對齊)Struct結構體記憶體
- 探索 Go 語言中的記憶體對齊:為什麼結構體大小會有所不同?Go記憶體結構體
- 記憶體對齊巨集定義的簡明解釋記憶體
- C結構體中資料的記憶體對齊問題結構體記憶體
- Go:記憶體管理與記憶體清理Go記憶體
- netcore高階知識點,記憶體對齊,原理與示例NetCore記憶體
- Go記憶體逃逸分析Go記憶體
- go中的記憶體逃逸Go記憶體
- go記憶體分配器Go記憶體
- Go記憶體管理逃逸分析Go記憶體
- 【轉載】ARM嵌入式系統為什麼要做記憶體對齊記憶體
- #pragma pack記憶體對齊的實現以及相關微軟面試題記憶體微軟面試題
- 實戰Go記憶體洩露Go記憶體洩露
- 遊戲記憶體對比普通記憶體區別 遊戲記憶體和普通記憶體相差大嗎?遊戲記憶體
- JavaScript對記憶體的使用JavaScript記憶體
- 圖解Go語言記憶體分配圖解Go記憶體
- Pprof定位Go程式記憶體洩露Go記憶體洩露
- Go記憶體管理一文足矣Go記憶體
- Go記憶體分配和GC的理解Go記憶體GC
- GO slice 切片-在記憶體中如何分配Go記憶體
- Go Ballast 讓記憶體控制更加絲滑GoAST記憶體