"Gopferのフリーレン"に聞くGoのinterface{}サイズ
多分知っている人からすると当然のことしか言ってないと思うかな。でもいいねだけはしてくれると信じてるよ。勇者ヒンメルならそうしたってことだよ。
そんなわけで、今日は Go の interface{} のサイズと、そこに関係する 32ビット/64ビットアーキテクチャのお話だ。
そもそも 32ビットと 64ビットの違いって?
まずは大前提として、32ビットアーキテクチャと 64ビットアーキテクチャの違いをざっくり見ておこうか。まぁ知ってる人には「そこから?」って言われるかもだけど、ヒンメルなら「誰かの役に立つはずさ」って言ってくれるんじゃないかな。
レジスタサイズ
- 32ビットアーキテクチャ: CPUの汎用レジスタが 32ビット幅(4バイト)
- 64ビットアーキテクチャ: CPUの汎用レジスタが 64ビット幅(8バイト)
レジスタは CPU がいちばん素早くアクセスできる場所だね。64ビットのほうが「一度に扱えるデータも増えるし、高速な処理ができる」ってわけさ。
メモリ空間
- 32ビット: 理論上 2³² = 4GB までメモリを扱える
- 64ビット: 理論上 2⁶⁴ = 16EB(エクサバイト)まで扱える
実際には OS や CPU の制限があるから 16EB のメモリを本当に積むことは難しい。でも「とにかく64ビットは広大なメモリ空間を扱える」って点を覚えておくといい。
命令セット
- 32ビットCPU: x86 や ARM32
- 64ビットCPU: x86-64 (AMD64) や AArch64 (ARM64)
64ビットCPU のほうが追加の汎用レジスタを利用できるから、関数コール時に引数をレジスタで渡せることが多くなるんだ。これも性能アップにつながる大きな要因だよ。
ポインタサイズ
- 32ビット: ポインタサイズは 4バイト
- 64ビット: ポインタサイズは 8バイト
ポインタのサイズが倍になると、メモリの使用量も増えるけど、一方で大きなメモリ空間を扱えるメリットがあるのさ。兵士が大きな武器を持つようなものだね。強くなるけど、それだけ装備が嵩張るという感じだろうか。
レジスタとかCPU周りについてもっと知りたかったらプログラムはなぜ動くのかを読むといいよ(後でフェルンにも読み聞かせなくちゃね)
Goのinterface{} のサイズ
じゃあいよいよ本題。Go で最もシンプルなインターフェース、interface{} ってのがあるよね。実はこの interface{} のサイズは、使っているアーキテクチャによって変わるんだ。
仕組み
interface{} は内部的に次の 2つのフィールドを持っている。
- type(型情報を指すポインタ)
- data(実際の値を指すポインタ、または値そのものが入ることもある)
どちらもポインタ型だから、32ビットでは 4バイトずつ、64ビットでは 8バイトずつになる。つまり
- 32ビット: 4バイト + 4バイト = 8バイト
- 64ビット: 8バイト + 8バイト = 16バイト
というわけさ。これが「Go の interface{} のサイズはアーキテクチャによって違う」一番の理由なんだ。ヒンメルも多分初めて知ったときは「へぇ」くらいは言ったかもしれないね。
実際にコードで確かめてみよう
ここまで聞いて「でも本当に? 実測してみなきゃ信じられない」と思う人もいるだろうから、unsafe パッケージを使ってサイズを確認してみようか。
package main
import (
"fmt"
"unsafe"
)
func main() {
var i interface{}
fmt.Println("Size of empty interface:", unsafe.Sizeof(i))
}
このコードを 32ビット環境でコンパイル&実行すれば、おそらく 8 と表示されるだろう。逆に 64ビット環境で走らせると 16 と出てくるはずだ。もし 16 じゃなかったら、私の話を疑ってくれて構わない。
64ビットで実行
GOARCH=amd64 go run main.go
出力結果
Size of empty interface: 16
32ビットで実行
GOARCH=386 go run main.go
出力結果
Size of empty interface: 8
※コンパイラや実行環境の設定によっては、違う出力が得られることもごくまれにあるかもしれないが、基本的には上記の結果が得られるはず。
まとめ
- 32ビットと64ビットの違い
- レジスタサイズ、メモリ空間、命令セット、ポインタサイズに差がある
- 64ビットのほうが大きなメモリを扱え、効率的なレジスタ渡しが期待できる
- Go の interface{} のサイズ
- 2つのポインタ(型情報 + データ)を内部的に保持
- 32ビットなら 8バイト、64ビットなら 16バイト
アーキテクチャ | type フィールド | data フィールド | 合計 |
---|---|---|---|
32ビット | 4バイト (ポインタ) | 4バイト (ポインタ) | 8バイト |
64ビット | 8バイト (ポインタ) | 8バイト (ポインタ) | 16バイト |
まぁ、知ってる人にとっては「それがどうした」って話かもしれないけど、こうやって少しずつ知識を蓄えていくのが大事ってもんさ。ヒンメルなら「面白い話じゃないか」って言ってくれるかな? 少なくとも、いいねボタンくらいは押してくれると信じたいね。
Discussion