1 of 23

Go言語の

スタックとヒープ

najeira @ GoCon 2013 Autumn

2 of 23

リンク

http://najeira.blogspot.jp/2013/10/go.html

スライドだと口頭で説明した内容が欠けているので、上記のブログ記事に分かりやすくまとめました。

3 of 23

メモリ領域

  • スタック
  • ヒープ

他:

プログラム領域(命令コード)

スタティック(グローバル変数/定数)

4 of 23

スタック

  • ローカル変数
  • 関数の引数、戻り値
  • リターンアドレス

コールスタック

FILO/LIFO

5 of 23

ヒープ

  • malloc, new

heap: たくさん、山積み

6 of 23

特徴

スタック

  • 速い(確保/解放)
  • 一時的
    • 関数を抜けるまで
  • 小さい

ヒープ

  • 遅い(確保/解放)
  • 寿命は自由
  • 大きい

ヒープのメモリ確保は遅い

⇒可能であればスタックを使う

7 of 23

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

}

8 of 23

ヒープのメモリ確保を避けるなら:

func getSize(w, h int) Size {

s := Size{}

s.Width = w

s.Height = h

return s

}

ポインタ型ではなく値型

ポインタ型ではなく値型

9 of 23

結論を先に言うと……

  • 通常は意識する必要なし
  • コンパイラが賢く判断してくれる

10 of 23

せっかくなので

もう少し

調べてみる

11 of 23

-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

12 of 23

ローカル変数

ローカル変数は、基本的にスタック

func test() {

n := 123

...

13 of 23

アドレスを使う (1)

アドレスを使うと、ヒープになったりする

func test() {

n := 123

fmt.Println(&n) // 0x21015c018

14 of 23

アドレスを使う (2)

アドレスの使い方によっては、

関数内におさまるのでスタック

func test() {

n := 123

np := &n

fmt.Println(*np) // 123

15 of 23

アドレスを使う (3)

ちなみに、

ローカル変数のアドレスを返してよい

func test() *int {

n := 123

return &n // ヒープ

}

※C言語などでは禁じ手

16 of 23

どうやら

関数内におさまるか

関数外で使われるか

コンパイラが適切に判断

良きに計らう

17 of 23

new (1)

newしても、関数内におさまるのでスタック

func test() {

np := new(int)

*np = 123

fmt.Println(*np)

}

※newしたからヒープというわけではない

18 of 23

new (2)

関数内におさまらないので、ヒープ

func test() *int {

np := new(int)

*np = 123

return np

}

19 of 23

array (1)

スタック

func test() {

a := []int{1, 2, 3}

fmt.Println(len(a))

}

20 of 23

array (2)

ヒープ

func test() []int {

a := []int{1, 2, 3}

return a

}

21 of 23

slice, map

slice: make([]int, 100)

map: map[int]string{1: "a", 2: "b"}

などでもarrayと同様

22 of 23

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()

}

23 of 23

結論

  • 通常は意識する必要なし
  • コンパイラが賢く判断してくれる

Goコンパイラ賢い!

賢いよ!

※見た目はアレだけど