Goのメモリアライメント ~Goの構造体におけるメモリ使用量の最適化~

株式会社Schooの [@hiroto_0411](https://x.com/hiroto_0411)です！
Goのメモリアライメントと構造体のフィールドを定義する順番によるメモリ使用量の変化について調査してみました！

## 簡単にまとめると
-  CPUが効率的に処理できるよう、メモリアライメントにより変数のアドレスは特定の倍数になるように調整される
-  構造体のフィールドを定義する順番によって、メモリ使用量は変化する

## 型それぞれのサイズ
```go
func main() {
	var num1 int64
	fmt.Printf("Size of int64: %d bytes\n", unsafe.Sizeof(num1))

	var num2 int32
	fmt.Printf("Size of int32: %d bytes\n", unsafe.Sizeof(num2))

	var num3 float64
	fmt.Printf("Size of float64: %d bytes\n", unsafe.Sizeof(num3))

	var num4 float32
	fmt.Printf("Size of float32: %d bytes\n", unsafe.Sizeof(num4))

	var bool bool
	fmt.Printf("Size of bool: %d bytes\n", unsafe.Sizeof(bool))

	var str string
	fmt.Printf("Size of string: %d bytes\n", unsafe.Sizeof(str))

	var slice []int
	fmt.Printf("Size of slice: %d bytes\n", unsafe.Sizeof(slice))

}
```

```
Size of int64: 8 bytes
Size of int32: 4 bytes
Size of float64: 8 bytes
Size of float32: 4 bytes
Size of bool: 1 bytes
Size of string: 16 bytes
Size of slice: 24 bytes
```

## 変数とアドレス
64 bitシステムのCPUは8 bytesずつ処理していく。
CPUが効率的に処理できるよう、メモリアライメントにより変数のアドレスは特定の倍数になるように調整される。

<img width="500" alt="mermaid" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810533/d7f2a34c-c42f-b384-1588-d985683793d0.png">

int32は4 bytesのためアドレスが4の倍数になるように配置される。
<br />

<img width="500" alt="mermaid" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810533/765acaba-e565-674a-6ad2-fad75fb84c46.png">

この図のように4の倍数以外の箇所に配置すると、CPUが1サイクルで処理でき無くなる可能性があり効率が悪くなってしまう。

### Goのコードで確認してみる
```go
func main() {
	var num1 int64
	fmt.Printf("Memory address of num1: %p\n", &num1) 

	var num2 int32
	fmt.Printf("Memory address of num2: %p\n", &num2) 
}
```
出力
```
Memory address of num1: 0x14000120018
Memory address of num2: 0x14000120030
```
0x14000120018(16進数)→ 1374390714392(10進数)　8 bytes必要なint64はアドレスが8の倍数になるところへ配置される。
0x14000120030(16進数)→ 1374390714416(10進数)　4 bytes必要なint32はアドレスが4の倍数になるところへ配置される。

**メモリアライメント**
CPUが効率的にアクセスできるよう変数のアドレスを調整すること。

**参考**

https://medium.com/@manishpokhriyal5/data-alignment-technique-to-optimise-memory-speed-in-go-460da08f466d


## 構造体とメモリアライメント
```go
type User struct {
	ID    int32  // 4 bytes
	name  string // 16 bytes
	isAdmin bool   // 1 byte
}

func main() {
	user := User{ID: 1, name: "John", isAdmin: true}
	fmt.Println(unsafe.Sizeof(user))
	fmt.Printf("Memory address of ID: %p\n", &user.ID)
	fmt.Printf("Memory address of name: %p\n", &user.name)
	fmt.Printf("Memory address of isAdmin: %p\n", &user.isAdmin)
}
```
出力
```
32
Memory address of ID: 0x14000128000
Memory address of name: 0x14000128008
Memory address of isAdmin: 0x14000128018
```
0x14000128000 → 1374390747136 (10進数)
0x14000128008 → 1374390747144 (10進数)
0x14000128008 → 1374390747160 (10進数)

計算上だと構造体のサイズは4+16+1=21 bytesのはずだが、実際は32 bytesとなっている。これはメモリアライメントによって変数の配置される場所が調整されているためである。

### イメージ

<img width="700" alt="image1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810533/ee8d0c36-897a-d726-b5bd-103ebe622118.png
">
青色がデータを保持している箇所、赤色が調整するために加えられたpadding。
- IDはint32なので、4 bytes使用する
- nameはstringなので16bytes(8 bytes*2)使用するため、8の倍数となるメモリに配置する必要があり、IDの後に4 bytesのメモリがpaddingとして追加される
- isAdminはboolなので1 byteだけ使用するため、どこにでも配置できる


### 構造体のフィールド順番を変更してみる
```go
type User struct {
	name  string // 16 bytes
	ID    int32  // 4 bytes
	isAdmin bool   // 1 byte
}

func main() {
	user := User{ID: 1, name: "John", isAdmin: true}
	fmt.Println(unsafe.Sizeof(user))
	fmt.Printf("Memory address of name: %p\n", &user.name)
	fmt.Printf("Memory address of ID: %p\n", &user.ID)
	fmt.Printf("Memory address of isAdmin: %p\n", &user.isAdmin)
}
```

出力
```
24
Memory address of name: 0x14000128000
Memory address of ID: 0x14000128010
Memory address of isAdmin: 0x14000128014
```
0x14000128000 → 1374390747136 (10進数)
0x14000128010 → 1374390747152 (10進数)
0x14000128014 → 1374390747156 (10進数)

サイズが先ほどは32 bytesだったが、24 bytesとなっておりメモリを効率的に使うことができている。

### イメージ
<img width="700" alt="image2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810533/d691cce7-eedb-86da-f934-5cded8d78fb5.png">


- nameはstringなので16 bytes(8 bytes*2)使用する
- IDはint32なので、4 bytes使用する
- isAdminはboolなので1 byte使用するため、IDの後に続けて配置することができる


**このように、構造体のフィールドの順番を変えることでメモリ使用量を最適化することができる。**

## 参考

https://blog.stackademic.com/structure-matters-padding-alignment-and-memory-optimization-in-go-892072dcd88f

https://www.linkedin.com/pulse/golang-padding-alignment-memory-management-ahmed-ghazey

https://medium.com/techverito/golang-struct-size-and-memory-optimisation-b46b124f008d

https://medium.com/@manishpokhriyal5/data-alignment-technique-to-optimise-memory-speed-in-go-460da08f466d

---
**Schooでは一緒に働く仲間を募集しています！**

https://corp.schoo.jp/careers/graduates

https://corp.schoo.jp/careers