Go 之內存對齊

爲什麼要對齊

對齊原因:

1)平臺原因 (移植性原因):

不是所有的硬件平臺都能夠訪問任意地址上的任意數據的。例如,某些平臺只允許在某些特定地址處獲取特定類型的數據,否則會拋出異常。

2)性能原因:

實際上 CPU 並不會以一個一個字節去讀取和寫入內存。相反 CPU 讀取內存是一****塊一塊讀取的。塊大小 (2、4、6、8、16 等字節大小) 我們稱其爲內存訪問粒度

如果訪問未對齊的內存,將導致 cpu 進行兩次內存訪問,並且需要花費額外的時間來處理對齊和運算。

默認係數

在不同平臺上的編譯器都有自己默認的 “對齊係數”,可通過預編譯命令 #pragma pack(n) 進行變更,n 就是代指 “對齊係數”。一般來講,我們常用的平臺的係數如下:

    32 位:4

    64 位:8

使用函數Alignof()、Offsetof()返回相應類型的對齊係數、偏移量:

xE9NDo

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 來分析,boolint8int32int64、byte、[]int32string對齊值分別是 1、1、4、8、1、`8`、8,佔用內存大小分別是 1、1、4、8、1、`24`、16,我們先根據第一條對齊規則分析stu1

特別注意

空 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