10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-10-04

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

簡単にまとめると

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

型それぞれのサイズ

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が効率的に処理できるよう、メモリアライメントにより変数のアドレスは特定の倍数になるように調整される。

mermaid

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

mermaid

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

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が効率的にアクセスできるよう変数のアドレスを調整すること。

参考

構造体とメモリアライメント

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となっている。これはメモリアライメントによって変数の配置される場所が調整されているためである。

イメージ

image1
青色がデータを保持している箇所、赤色が調整するために加えられたpadding。

  • IDはint32なので、4 bytes使用する
  • nameはstringなので16bytes(8 bytes*2)使用するため、8の倍数となるメモリに配置する必要があり、IDの後に4 bytesのメモリがpaddingとして追加される
  • isAdminはboolなので1 byteだけ使用するため、どこにでも配置できる

構造体のフィールド順番を変更してみる

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となっておりメモリを効率的に使うことができている。

イメージ

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

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

参考


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

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?