はじめに
Goを使っていて、「この変数って、実際にはどれくらいメモリを使っているんだろう?」と疑問を感じたことはありませんか?
この記事ではGoの各データ型が実際にどのくらいのメモリ領域を確保するのか、具体的なサンプルコードと実行結果を交えて検証していきます。
型サイズを把握しておくことで、不要なメモリ消費を抑え、より効率的なコードが書けるようになるはずです。
なお、この記事におけるGoのバージョンはgo version go1.24.1 darwin/amd64です。
【Go】データ型別に確保されるメモリ領域まとめ
Goでは格納する値の内容に関わらず、型ごとに確保されるメモリ領域が決まっています。
ここでは、unsafe
パッケージのSizeof
関数を使って各型のサイズを検証していきましょう。
プリミティブ型
まずは基本的なデータ型(プリミティブ型)から見ていきます。
package main
import (
"fmt"
"unsafe"
)
func main() {
fmt.Printf("byte: %dバイト\n", unsafe.Sizeof(byte(0)))
fmt.Printf("uint: %dバイト\n", unsafe.Sizeof(uint(0)))
fmt.Printf("uint8: %dバイト\n", unsafe.Sizeof(uint8(0)))
fmt.Printf("uint16: %dバイト\n", unsafe.Sizeof(uint16(0)))
fmt.Printf("uint32: %dバイト\n", unsafe.Sizeof(uint32(0)))
fmt.Printf("uint64: %dバイト\n", unsafe.Sizeof(uint64(0)))
fmt.Printf("uintptr: %dバイト\n", unsafe.Sizeof(uintptr(0)))
fmt.Printf("int: %dバイト\n", unsafe.Sizeof(0))
fmt.Printf("int8: %dバイト\n", unsafe.Sizeof(int8(0)))
fmt.Printf("int16: %dバイト\n", unsafe.Sizeof(int16(0)))
fmt.Printf("int32: %dバイト\n", unsafe.Sizeof(int32(0)))
fmt.Printf("int64: %dバイト\n", unsafe.Sizeof(int64(0)))
fmt.Printf("float32: %dバイト\n", unsafe.Sizeof(float32(0)))
fmt.Printf("float64: %dバイト\n", unsafe.Sizeof(float64(0)))
fmt.Printf("complex64: %dバイト\n", unsafe.Sizeof(complex64(0)))
fmt.Printf("complex128: %dバイト\n", unsafe.Sizeof(complex128(0)))
fmt.Printf("string: %dバイト\n", unsafe.Sizeof(""))
fmt.Printf("rune: %dバイト\n", unsafe.Sizeof('a'))
fmt.Printf("bool: %dバイト\n", unsafe.Sizeof(false))
}
実行結果は次のとおりです。
go-sample $ go run main.go
byte: 1バイト
uint: 8バイト
uint8: 1バイト
uint16: 2バイト
uint32: 4バイト
uint64: 8バイト
uintptr: 8バイト
int: 8バイト
int8: 1バイト
int16: 2バイト
int32: 4バイト
int64: 8バイト
float32: 4バイト
float64: 8バイト
complex64: 8バイト
complex128: 16バイト
string: 16バイト
rune: 4バイト
bool: 1バイト
array
array
は要素数と要素の型サイズによって確保するメモリ領域が変わります。
package main
import (
"fmt"
"unsafe"
)
func main() {
fmt.Printf("array1: %dバイト\n", unsafe.Sizeof([1]int{1}))
fmt.Printf("array2: %dバイト\n", unsafe.Sizeof([3]int8{1, 2, 3}))
fmt.Printf("array3: %dバイト\n", unsafe.Sizeof([5]string{"a", "b", "c", "d", "e"}))
}
実行結果は下記のとおりです。
go-sample $ go run main.go
array1: 8バイト
array2: 3バイト
array3: 80バイト
このように、要素の型サイズに要素数をかけ合わせた分が、arrayの型サイズとなるのです。
slice, map, chan
slice
, map
, chan
は要素の型サイズや要素数にかかわらず、固定の型サイズになります。
fmt.Printf("slice1: %dバイト\n", unsafe.Sizeof([]int{1}))
fmt.Printf("slice2: %dバイト\n", unsafe.Sizeof([]int8{1, 2, 3}))
fmt.Printf("slice3: %dバイト\n", unsafe.Sizeof([]string{"a", "b", "c", "d", "e"}))
fmt.Printf("map1: %dバイト\n", unsafe.Sizeof(map[string]int{"a": 1}))
fmt.Printf("map2: %dバイト\n", unsafe.Sizeof(map[string]int8{"a": 1, "b": 2, "c": 3}))
fmt.Printf("map3: %dバイト\n", unsafe.Sizeof(map[int]bool{1: true, 2: false, 3: true, 4: false, 5: true}))
fmt.Printf("chan1: %dバイト\n", unsafe.Sizeof(make(chan int, 1)))
fmt.Printf("chan2: %dバイト\n", unsafe.Sizeof(make(chan int8, 3)))
fmt.Printf("chan3: %dバイト\n", unsafe.Sizeof(make(chan string, 5)))
実行結果は次のとおりです。
go-sample $ go run main.go
slice1: 24バイト
slice2: 24バイト
slice3: 24バイト
map1: 8バイト
map2: 8バイト
map3: 8バイト
chan1: 8バイト
chan2: 8バイト
chan3: 8バイト
slice
は24バイト、map
は8バイト、chan
は8バイトで固定されていることがわかります。
構造体
構造体の型サイズは各フィールドの型サイズの合計となります。
サンプルコードで確認します。
package main
import (
"fmt"
"unsafe"
)
type A struct {
i int // 8バイト
s string // 16バイト
f float64 // 8バイト
}
type B struct {
i int // 8バイト
a A // 8 + 16 + 8 = 32バイト
}
func main() {
a := A{}
fmt.Printf("A: %dバイト\n", unsafe.Sizeof(a))
b := B{}
fmt.Printf("B: %dバイト\n", unsafe.Sizeof(b))
}
実行結果は次のとおりです。
go-sample $ go run main.go
A: 32バイト
B: 40バイト
構造体A, Bともに各フィールドの型サイズの合計であることがわかります。
※あ
ポインタ
64bitマシンの場合、ポインタの型サイズは常に8バイトになります。
package main
import (
"fmt"
"unsafe"
)
type A struct {
i int
s string
f float64
}
type B struct {
i int
a A
}
func main() {
a := A{}
fmt.Printf("A: %dバイト\n", unsafe.Sizeof(&a))
b := B{}
fmt.Printf("B: %dバイト\n", unsafe.Sizeof(&b))
}
実行結果は次のとおりです。
go-sample $ go run main.go
A: 8バイト
B: 8バイト
interface
interface
は常に固定で16バイトのメモリ領域を確保します。
package main
import (
"fmt"
"unsafe"
)
type MultiMethod interface {
MethodA() string
MethodB() int
MethodC(float64) bool
}
type X struct{}
func (x X) MethodA() string {
return "MethodA"
}
func (x X) MethodB() int {
return 42
}
func (x X) MethodC(f float64) bool {
return f > 0
}
func main() {
var m MultiMethod = X{}
fmt.Printf("MultiMethodを格納したインターフェース: %dバイト\n", unsafe.Sizeof(m))
}
実行結果は次のとおりです。
go-sample $ go run main.go
MultiMethodを格納したインターフェース: 16バイト
おわりに
この記事では、Goの主要なデータ型がどのようにメモリ領域を確保するかを実例を交えて紹介しました。
メモリ消費を意識する場面ではunsafe.Sizeof
を活用して、実際のサイズを確認してみてください。
記事の内容に誤りがあれば、コメントにて教えていただけますと幸いです。