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: 1B
- int32: 4B
- int8: 1B
- int64: 8B
- byte: 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. 完美.
當然, 此工具的功能不僅如此, 它能夠提供很多建議, 有待發掘.
其實, 項目中估計也很少有關注內存對齊的時候吧. 不過畢竟積少成多, 內存這玩意, 能省則省嘛.
總結
- 上一篇: 6大设计原则之里氏替换原则
- 下一篇: webshell提权教程linux,Li