summary
- The escaped pointer variable itself is also allocated in the heap space, so the function can return the address of the local variable. At this time, the local variable is equivalent to the local pointer variable. When escaping, the pointer variable itself is also allocated in the heap space, so it can return its address.
- The memory of stack space is managed by the compiler, and the allocation and release speed is very fast. Heap space is managed by gc. Frequent gc will occupy a large system overhead. stop the world
- Escape analysis is done by the compiler at static compilation time.
- If the slice variable itself escapes, its underlying data area will also escape. Even if the slice length is small.
- The slice variable itself does not escape. Generally, its data area is also on the stack. If the length is too long, the data area will be allocated to the heap, but the slice variable itself is still on the stack.
How to determine memory escape?
go run -gcflags '-m -l' main.go
Note: the above command only has output when the compiler accesses and judges whether to escape.
Sometimes the stack space is smaller than the heap space address. I don't know why.
Examples of memory escape
func main() { a := 1 _ = a } // No output
func main() { a := 1 fmt.Printf("%p\n", &a) fmt.Println(a) b := 2 fmt.Printf("%p\n", &b) fmt.Println(b) c := 3 fmt.Printf("%p\n", &c) fmt.Println(c) d := 4 fmt.Printf("%p\n", &d) fmt.Println(d) } // output ./main.go:10:2: moved to heap: a ./main.go:14:2: moved to heap: b ./main.go:18:2: moved to heap: c ./main.go:22:2: moved to heap: d ./main.go:11:12: ... argument does not escape ./main.go:12:13: ... argument does not escape ./main.go:12:13: a escapes to heap ./main.go:15:12: ... argument does not escape ./main.go:16:13: ... argument does not escape ./main.go:16:13: b escapes to heap ./main.go:19:12: ... argument does not escape ./main.go:20:13: ... argument does not escape ./main.go:20:13: c escapes to heap ./main.go:23:12: ... argument does not escape ./main.go:24:13: ... argument does not escape ./main.go:24:13: d escapes to heap 0xc000012080 1 0xc000012088 2 0xc0000120a0 3 0xc0000120a8 4
fmt.Printf function abc is assigned to interface {}, causing escape. The abc address is growing in the heap space, as shown in the figure, which confirms that it is on the heap. Compare the following:
func main() { a := 1 println(&a) println(a) b := 2 println(&b) println(b) c := 3 println(&c) println(c) d := 4 println(&d) println(d) } // output 0xc00002e768 1 0xc00002e760 2 0xc00002e758 3 0xc00002e750 4
println does not trigger memory escape. abc is allocated in stack space, which is confirmed by the increase of address from high to low.
The four pointers do not escape:
func main() { a := new(int) println(&a) println(a) println("======") b := new(int) println(&b) println(b) println("======") c := new(int) println(&c) println(c) println("======") d := new(int) println(&d) println(d) } // output ./main.go:32:10: new(int) does not escape ./main.go:39:10: new(int) does not escape ./main.go:46:10: new(int) does not escape ./main.go:53:10: new(int) does not escape 0xc00002e768 0xc00002e740 ====== 0xc00002e760 0xc00002e738 ====== 0xc00002e758 0xc00002e730 ====== 0xc00002e750 0xc00002e748
The abcd pointer variable itself and the memory area it refers to do not escape. They are allocated in the stack space. The printed address is a continuous area from large to small, which confirms this.
The memory allocation is shown in the figure below:
The four pointers have escaped
func main() { a := new(int) fmt.Printf("%p\n", &a) fmt.Println(a) println("======") b := new(int) fmt.Printf("%p\n", &b) fmt.Println(b) println("======") c := new(int) fmt.Printf("%p\n", &c) fmt.Println(c) println("======") d := new(int) fmt.Printf("%p\n", &d) fmt.Println(d) println("======") f := new(int) println(&f) println(f) } //output 0xc00000e028 0xc000012080 ====== 0xc00000e038 0xc000012088 ====== 0xc00000e040 0xc0000120a0 ====== 0xc00000e048 0xc0000120a8 ====== 0xc000058f38 0xc000058f30
In the case of escape, the abcd pointer variable itself and the memory area it refers to are allocated in the heap space, and the printed address is a continuous area from small to large. The f pointer variable and the memory area it refers to are allocated in the stack space, and its address is higher than the heap area address, which proves this.
The escaped pointer variable itself is also allocated in heap space, so the function can return local pointer variables
The memory allocation diagram is as follows:
func main() { a := new(int) _ = a } // output ./main.go:8:10: main new(int) does not escape
Even if the new variable does not escape, it is allocated on the stack
- The println function does not trigger escape, but fmt.Printf does because its function parameter is of interface {} type
- Note: in the above example, a is the pointer variable allocated in the stack space, and the int storage unit pointed to is in the next byte (also in the stack space).
- Stack space is allocated from high to low at high address.
If this is written in C + +, it is a typical error: return the address of the local variable, and the content of the address will be automatically released after the function exits, because it is on the stack.
So is the local variable of go language on the stack or on the heap? The go language compiler will do escape analysis to analyze whether the scope of local variables escapes from the scope of the function. If not, it will be placed on the stack; If the scope of the variable exceeds the scope of the function, it is automatically placed on the heap.
Test and observe the address change of g
func main { var g *int println(&g) println(g) g = new(int) println(&g) println(g) g = new(int) println(&g) println(g) g = new(int) fmt.Println(&g) fmt.Println(g) }
Without the last two lines, there is no escape. The memory pointed to by g and g is in the stack area, and if added, it is in the heap area.
There is a scene of escape
package main type Student struct { Name interface{} } func main() { stu := new(Student) stu.Name = "tom" }
Escape occurs when interface {} is assigned. The optimization scheme is to set the type to a fixed type, such as string
package main type Student struct { Name string } func GetStudent() *Student { stu := new(Student) stu.Name = "tom" return stu } func main() { GetStudent() }
If the pointer type (address of local variable) is returned, escape will occur, and the optimization scheme depends on the situation.
Is it efficient for functions to pass pointers and values? We know that passing the pointer can reduce the copy of the underlying value and improve the efficiency. However, if the amount of copied data is small, the pointer passing will cause escape, which may use the heap or increase the burden of GC. Therefore, passing the pointer is not necessarily efficient.
Do not blindly use variable pointers as parameters. Although replication is reduced, the overhead of variable escape may be greater.
func main() { nums := make([]int, 5, 5) nums2 := make([]int, 5, 5) println(&nums) println(nums) println(&nums2) println(nums2) for i := range nums { nums[i] = i println(&nums[i]) } println("======2") for i := range nums2 { nums2[i] = i println(&nums2[i]) } println("======3") nums3 := make([]int, 10000, 10000) println(&nums3) println(nums3) for i := range nums3 { if i == 5 { break } nums3[i] = i println(&nums3[i]) } //fmt.Println(&nums3) } // output ./main.go:8:14: make([]int, 5, 5) does not escape ./main.go:9:15: make([]int, 5, 5) does not escape ./main.go:25:15: make([]int, 10000, 10000) escapes to heap 0xc00002e758 [5/5]0xc00002e6f8 0xc00002e740 [5/5]0xc00002e6d0 0xc00002e6f8 0xc00002e700 0xc00002e708 0xc00002e710 0xc00002e718 ======2 0xc00002e6d0 0xc00002e6d8 0xc00002e6e0 0xc00002e6e8 0xc00002e6f0 ======3 0xc00002e728 [10000/10000]0xc000054000 0xc000054000 0xc000054008 0xc000054010 0xc000054018 0xc000054020
As above: the first and second slice variables and their underlying data pointer areas are allocated on the stack.
For the third super slice, the slice variable itself is allocated on the stack, and the underlying data is on the heap.
If the last fmt.Printf is commented out, the third slice variable itself will escape and be allocated on the heap.
Note: the address space of the 1st to nth elements in the slice data area increases from 0 to up, whether on the stack or on the heap.
func main() { var slice1 []int println(&slice1) println(slice1) fmt.Printf("%p\n", &slice1) println(&slice1) println(slice1) slice1 = make([]int,5,5) println(&slice1) println(slice1) } // output ./main.go:11:6: moved to heap: slice1 ./main.go:14:12: ... argument does not escape ./main.go:17:15: make([]int, 5, 5) escapes to heap 0xc0000a4018 [0/0]0x0 0xc0000a4018 0xc0000a4018 [0/0]0x0 0xc0000a4018 [5/5]0xc0000aa030
If the slice variable itself escapes, its underlying data area will also escape. Even if the slice length is small.
The slice variable itself does not escape. Generally, its data area is also on the stack. If the length is too long, the data area will be allocated to the heap, but the slice variable itself is still on the stack.
package main func main() { nums := make([]int, 10000, 10000) for i := range nums { nums[i] = i } }
If the slice is too large, escape will occur. Set the capacity as much as possible in the optimization scheme. If the capacity is too large, there is no way.
Elements in a map cannot be addressed.
When the map does not escape, it is also allocated on the stack, the variable itself and the underlying data area.