Go言語の
スタックとヒープ
najeira @ GoCon 2013 Autumn
リンク
メモリ領域
他:
プログラム領域(命令コード)
スタティック(グローバル変数/定数)
スタック
コールスタック
FILO/LIFO
ヒープ
heap: たくさん、山積み
特徴
スタック
ヒープ
ヒープのメモリ確保は遅い
⇒可能であればスタックを使う
例
func test() {
for i := 0; i < 100; i++ {
s := getSize(i, i) // hot spot!
fmt.Println(s.Width * s.Height)
}
}
func getSize(w, h int) *Size {
s := new(Size) // heap!
s.Width = w
s.Height = h
return s
}
※getSizeがtest内にインライン展開されるので、
この例は実はヒープではない……
type Size struct {
Width int
Height int
}
例
ヒープのメモリ確保を避けるなら:
func getSize(w, h int) Size {
s := Size{}
s.Width = w
s.Height = h
return s
}
ポインタ型ではなく値型
ポインタ型ではなく値型
結論を先に言うと……
せっかくなので
もう少し
調べてみる
-gcflags -m
コンパイラにフラグを渡して詳細を知る
> go build -gcflags -m hello.go
./hello.go:10: moved to heap: n
./hello.go:11: &n escapes to heap
./hello.go:17: m dones not escape
ローカル変数
ローカル変数は、基本的にスタック
func test() {
n := 123
...
アドレスを使う (1)
アドレスを使うと、ヒープになったりする
func test() {
n := 123
fmt.Println(&n) // 0x21015c018
アドレスを使う (2)
アドレスの使い方によっては、
関数内におさまるのでスタック
func test() {
n := 123
np := &n
fmt.Println(*np) // 123
アドレスを使う (3)
ちなみに、
ローカル変数のアドレスを返してよい
func test() *int {
n := 123
return &n // ヒープ
}
※C言語などでは禁じ手
どうやら
関数内におさまるか
関数外で使われるか
コンパイラが適切に判断
良きに計らう
new (1)
newしても、関数内におさまるのでスタック
func test() {
np := new(int)
*np = 123
fmt.Println(*np)
}
※newしたからヒープというわけではない
new (2)
関数内におさまらないので、ヒープ
func test() *int {
np := new(int)
*np = 123
return np
}
array (1)
スタック
func test() {
a := []int{1, 2, 3}
fmt.Println(len(a))
}
array (2)
ヒープ
func test() []int {
a := []int{1, 2, 3}
return a
}
slice, map
slice: make([]int, 100)
map: map[int]string{1: "a", 2: "b"}
などでもarrayと同様
interface
type Duck struct{}
func (d *Duck) Sound() {
fmt.Println("quack")
}
type Sounder interface {
Sound()
}
func main() {
// スタック
d := Duck{}
d.Sound()
// ヒープ
var s Sounder = &Duck{}
s.Sound()
}
結論
Goコンパイラ賢い!
賢いよ!
※見た目はアレだけど