Go 之內存對齊
爲什麼要對齊
對齊原因:
1)平臺原因 (移植性原因):
不是所有的硬件平臺都能夠訪問任意地址上的任意數據的。例如,某些平臺只允許在某些特定地址處獲取特定類型的數據,否則會拋出異常。
2)性能原因:
實際上 CPU 並不會以一個一個字節去讀取和寫入內存。相反 CPU 讀取內存是一****塊一塊讀取的。塊大小 (2、4、6、8、16 等字節大小) 我們稱其爲內存訪問粒度。
如果訪問未對齊的內存,將導致 cpu 進行兩次內存訪問,並且需要花費額外的時間來處理對齊和運算。
默認係數
在不同平臺上的編譯器都有自己默認的 “對齊係數”,可通過預編譯命令 #pragma pack(n)
進行變更,n 就是代指 “對齊係數”。一般來講,我們常用的平臺的係數如下:
32 位:4
64 位:8
使用函數Alignof()、Offsetof()
返回相應類型的對齊係數、偏移量:
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("slice align: %d\n", unsafe.Alignof([]int{1,2,3 }))
fmt.Printf("string align: %d\n", unsafe.Alignof("EDDYCJY"))
fmt.Printf("map align: %d\n", unsafe.Alignof(map[string]string{}))
}
//對齊值分別爲:
bool align: 1
int32 align: 4
int8 align: 1
int64 align: 8
byte align: 1
slice align: 8
string align: 8
map align: 8
對齊規則
1)結構體的成員變量, 第一個成員變量的偏移量是 0. 往後的每個成員變量的對齊值必須爲編譯器默認長度或當前成員變量類型的長度 (unsafe.Sizeof()), 取最小值作爲當前類型的對齊值。其偏移量必須爲當前成員變量的對齊值的整數倍;
2)結構體本身, 對齊值必須爲編譯器默認的對齊長度或結構體所有的成員變量類型中的最大長度, 取最大數的最小整數倍作爲對齊值。
(注:最大數指的是所有對齊長度中的最大值,或者所有成員變量長度中的最大值)
type stu struct {
a bool
b int8
c int32
d int64
e byte
g []int
f string
}
func main() {
stu1 := stu{}
fmt.Printf("stu1 size: %d, align:%d\n",unsafe.Sizeof(stu1), unsafe.Alignof(stu1))
//stu1 size: 64, align:8
}
使用的 64 位CPU
, 對齊參數是 8 來分析,bool
、int8
、int32
、int64
、byte、[]int32
、string
對齊值分別是 1、1、4、8、1、`8`、8
,佔用內存大小分別是 1、1、4、8、1、`24`、16
,我們先根據第一條對齊規則分析stu1
:
-
第一個字段類型是
bool
,對齊值是 1,大小爲 1,偏移量爲 0 ; -
第二個字段類型是
int8
,對齊值是 1,大小爲 1 ,根據規則一,所以他的內存偏移值必須是1
的倍數,前面不需填充 ; -
第三個字段類型是
int32,對齊值是 4,大小爲 4,所以他的內存偏移值必須是 4 的倍數,且前兩個字段排到 第`2`
位,第`3,4`位由編譯器進行填充,一般爲`0`值,也稱之爲空洞。第 5 位到第`8`位爲第三個字段 c ;
-
第四個字段類型是
int64
,對齊值是 8,大小爲 8,所以他的內存偏移值必須是 8 的倍數,因爲stu1
前三個字段就已經排到了第8
位,所以下一位的偏移量正好是 8,正好是字段 d 的對齊值的倍數,不用填充,可以直接排列第四個字段,也就是從第9
位到16
位第三個字段 d ; -
第五個字段類型是
bool
,對齊值是 1,大小爲 1,根據規則一,直接排第 17 位 ; -
第六個字段類型是
[]int
,對齊值是8
,大小爲24
,所以他的內存偏移值必須是 8 的倍數,因爲stu1
前面字段就已經排到了第17
位,我們目前的內存長度是17
, 不是字段e
的對齊值的倍數,所以從第18
位到 24 位需要填充。第24
位到 48 位第六個字段 e.; -
第七個字段類型是
string
,對齊值是8
,大小爲16
,所以他的內存偏移值必須是 8 的倍數,所以下一位的偏移量正好是48
。正好是字段 f 的對齊值的倍數,不用填充。 -
好了現在第一條內存對齊規則後,內存長度已經爲 64 字節,我們開始使用內存的第二條規則進行對齊。根據第二條規則,默認對齊值是
8
,字段中最大類型程度是24
,取最小的那一個,所以求出結構體的對齊值是8
,我們目前的內存長度是 64,是8
的倍數,所以不再需要補齊。
特別注意
空 struct{}
大小爲 0,作爲其他 struct 的字段時,一般不需要內存對齊。但有一種特許情況:即當 struct{}
作爲結構體最後一個字段時,需要內存對齊。
總結
1)內存對齊是爲了 cpu 更高效訪問內存數據;
2)結構體對齊依賴成員變量的大小保證和對齊保證;
3)地址對齊保證是:如果類型 t 的對齊保證是 n,那麼類型 t 的每個值的地址在運行時必須是 n 的倍數;
4)struct 內字段如果填充過度,可以嘗試重排,使字段排序更緊密,減少內存浪費;
5)零大小字段要避免作爲 struct 最後一個字段。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/C6992ViHvPwjJ2bZS6AA5A