Variable escape in Golang

Golang compiler variable escape analysis

The Go language compiler will automatically decide whether to put a variable on the stack or the heap.
The compiler will do escape analysis. When it is found that the scope of the variable does not run out of the function scope,
it can be placed on the stack, and vice versa. allocated on heap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

type Demo struct {
name string
}

func createDemo(name string) *Demo {
d := new(Demo)
d.name = name
return d
}

func main() {
demo := createDemo("demo")
fmt.Println(demo)
}

When compiling, you can use the option -gcflags=-m to check the situation of variable escape.

1
2
3
4
5
6
7
8
9
go build -gcflags=-m main.go
# command-line-arguments
./main.go:9:6: can inline createDemo
./main.go:16:20: inlining call to createDemo
./main.go:17:13: inlining call to fmt.Println
./main.go:9:17: leaking param: name
./main.go:10:10: new(Demo) escapes to heap
./main.go:16:20: new(Demo) escapes to heap
./main.go:17:13: ... argument does not escape

We can see that new(Demo) escapes to heap indicates that local variables escape to the heap

Escape rules

Generally, when we assign a value to a reference class member in a reference class object, escape may occur.
It can be understood that accessing a reference object is actually indirectly accessed through a pointer,
but if you access the reference member inside again, there will be a second indirect access.
If you operate this part of the object, escape is very likely to occur.

Reference types in Go language include func (function type), interface (interface type),
slice (slice type), map (dictionary type), channel (pipeline type), * (pointer type), etc.

Escape Example

  1. []interface{} data type, assignment through [] will inevitably cause escape.

    1
    2
    3
    4
    5
    6
    package main

    func main() {
    data := []interface{}{100, 200}
    data[0] = 100
    }

    Let’s take a look at the escape results through compilation

    1
    2
    3
    4
    5
    6
    7
    go build -gcflags=-m main.go
    # command-line-arguments
    ./main.go:3:6: can inline main
    ./main.go:4:23: []interface {}{...} does not escape
    ./main.go:4:24: 100 does not escape
    ./main.go:4:29: 200 does not escape
    ./main.go:5:2: 100 escapes to heap

    We can see that data[0] = 100 escapes.

  2. Escape example two

If the map[string]interface{} type attempts to assign a value, escape will definitely occur.

1
2
3
4
5
6
package main

func main() {
data := make(map[string]interface{})
data["key"] = 200
}

Let’s take a look at the escape results through compilation

1
2
3
4
5
 go build -gcflags=-m main.go
# command-line-arguments
./main.go:3:6: can inline main
./main.go:4:14: make(map[string]interface {}) does not escape
./main.go:5:2: 200 escapes to heap

We can see that data[“key”] = 200 escaped.

  1. Escape example three

The map[interface{}]interface{} type attempts to assign values, which will cause the assignment of key and value to escape.

1
2
3
4
5
6
package main

func main() {
data := make(map[interface{}]interface{})
data[100] = 200
}

Let’s take a look at the escape results through compilation

1
2
3
4
5
6
7
go build -gcflags=-m main.go
# command-line-arguments
./main.go:3:6: can inline main
./main.go:4:14: make(map[interface {}]interface {}) does not escape
./main.go:5:2: 100 escapes to heap
./main.go:5:2: 200 escapes to heap

We can see that in data[100] = 200, both 100 and 200 escape.

  1. Escape example four

map[string][]string data type, assignment will cause []string to escape.

1
2
3
4
5
6
package main

func main() {
data := make(map[string][]string)
data["key"] = []string{"value"}
}

Let’s take a look at the escape results through compilation

1
2
3
4
5
6
go build -gcflags=-m main.go
# command-line-arguments
./main.go:3:6: can inline main
./main.go:4:14: make(map[string][]string) does not escape
./main.go:5:24: []string{...} escapes to heap

We can see that the []string{…} slice escaped.

  1. Escape example five

[]*int data type, the rvalue of assignment will escape.

1
2
3
4
5
6
7
package main

func main() {
a := 10
data := []*int{nil}
data[0] = &a
}

Let’s take a look at the escape results through compilation

1
2
3
4
go build -gcflags=-m main.go
./main.go:3:6: can inline main
./main.go:4:2: moved to heap: a
./main.go:5:16: []*int{...} does not escape

Among them moved to heap: a, the variable a is finally moved to the heap.

  1. Escape example six

func(*int) function type, function assignment will cause the passed formal parameters to escape.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func foo(a *int) {
return
}

func main() {
data := 10
f := foo
f(&data)
fmt.Println(data)
}

Let’s take a look at the escape results through compilation

1
2
3
4
5
6
7
8
go build -gcflags=-m main.go
# command-line-arguments
./main.go:5:6: can inline foo
./main.go:12:3: inlining call to foo
./main.go:13:13: inlining call to fmt.Println
./main.go:5:10: a does not escape
./main.go:13:13: ... argument does not escape
./main.go:13:13: data escapes to heap

We will see that the data has been escaped to the heap.

  1. Escape example seven

func([]string): function type, assigning []string{“value”} will cause the passed parameters to escape.

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func foo(a []string) {
return
}

func main() {
s := []string{"aceld"}
foo(s)
fmt.Println(s)
}

Let’s take a look at the escape results through compilation

1
2
3
4
5
6
7
8
go build -gcflags=-m main.go
./main.go:5:6: can inline foo
./main.go:11:5: inlining call to foo
./main.go:12:13: inlining call to fmt.Println
./main.go:5:10: a does not escape
./main.go:10:15: []string{...} escapes to heap
./main.go:12:13: ... argument does not escape
./main.go:12:13: s escapes to heap

We see that s escapes to heap, s is escaped to the heap.

  1. Escape Example 8

chan []string data type, if you want to transmit []string{“value”} in the current channel, escape will occur.

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main() {
ch := make(chan []string)

s := []string{"aceld"}

go func() {
ch <- s
}()
}

Let’s take a look at the escape results through compilation

1
2
3
4
go build -gcflags=-m main.go
./main.go:8:5: can inline main.func1
./main.go:6:15: []string{...} escapes to heap
./main.go:8:5: func literal escapes to heap

We see that []string{…} escapes to heap, s is escaped to the heap.