Go escape analysis variables in heap or stack
References
https://www.youtube.com/watch?v=3D4o0MVs4Qo
https://www.kancloud.cn/aceld/golang/1958306
https://u.geekbang.org/subject/go?utm_identify=geektime&gk_cus_user_wechat=university
Stack memory
In computer system, heap and stack are abstracted areas in memory for storing program data
- The advantage of stack is faster access speed, but the disadvantage is lack of flexibility; The size and life cycle of the data in the stack are determined and consistent with the function. It is generally used to store function parameters and local variables, which are automatically allocated and released by the compiler
- The advantage of heap is that it can dynamically allocate memory size, which is suitable for memory allocation of unpredictable size, and has autonomy in the life cycle. The disadvantage is that complex memory allocation management will occupy resources, slow speed, memory fragmentation and heap memory leakage
Escape Analysis
According to the different characteristics of heap and stack, it is necessary to select an appropriate area to store the data in the program
For example, the memory management in C/C + + needs to be managed by programmers and developers
Most modern languages entrust the complex and error prone memory management to the compiler and parser, which makes the stack memory allocation transparent to programmers and reduces the development burden
One of the better technologies is escape analysis. The compiler can analyze the code characteristics and decide whether to use the heap or stack. As the name suggests, it is to escape the data that should be stored in the stack to the heap
The implementation of escape analysis technology in each language is similar. Java can also enable escape analysis through JVM parameters
Code case
Escape case of C code
There is no escape analysis in C language, so an error will be reported here. There is a wild pointer
#include<stdio.h> int* returnAddr() { //After the function ends, the stack memory will be released, and the pointer in memory returned here will become a wild pointer int a = 3; return &a; //Returns the memory address of a local variable } int main() { return 0; }
Execution result, error: forbidden to return local variables
test.cc:7:10: warning: address of stack memory associated with local variable 'a' returned [-Wreturn-stack-address] return &a; //Returns the memory address of a local variable ^ 1 warning generated.
Go code case
escape analysis
The code can run normally
The go compiler will decide whether the variables are stored on the heap or stack, which is escape analysis
If the scope of the variable does not run out of the function range, it can be on the stack, otherwise it is allocated on the heap
package main //Go build - gcflags' - M - L '. / main.go / / print escape analysis //Go tool compile - S. / main. Go / / print assembly code //Go tool compile - M. / main. Go / / print escape analysis func returnAddr() *int { //Escape to heap memory occurs a := 1 return &a //Returns a pointer to a local variable } func returnAddr1() int { //Will not escape to heap memory a := new(int) return *a //Returns the value of a pointer to a local variable } func main() { returnAddr() returnAddr1() }
Execution result analysis
# You can see the prompt in line 8 that the variable is moved to heap (moved to heap: a) $ go build -gcflags '-m -l' ./main.go # command-line-arguments ./main.go:8:2: moved to heap: a ./main.go:14:10: new(int) does not escape
Print assembly list to standard output
# Search the value of runtime.newobject keyword in heap space $ go tool compile -S main.go "".returnAddr STEXT size=79 args=0x8 locals=0x18 funcid=0x0 0x0000 00000 (main.go:6) TEXT "".returnAddr(SB), ABIInternal, $24-8 0x0000 00000 (main.go:6) MOVQ (TLS), CX 0x0009 00009 (main.go:6) CMPQ SP, 16(CX) 0x000d 00013 (main.go:6) PCDATA $0, $-2 0x000d 00013 (main.go:6) JLS 72 0x000f 00015 (main.go:6) PCDATA $0, $-1 0x000f 00015 (main.go:6) SUBQ $24, SP 0x0013 00019 (main.go:6) MOVQ BP, 16(SP) 0x0018 00024 (main.go:6) LEAQ 16(SP), BP 0x001d 00029 (main.go:6) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB) 0x001d 00029 (main.go:6) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x001d 00029 (main.go:8) LEAQ type.int(SB), AX 0x0024 00036 (main.go:8) MOVQ AX, (SP) 0x0028 00040 (main.go:8) PCDATA $1, $0 0x0028 00040 (main.go:8) CALL runtime.newobject(SB) 0x002d 00045 (main.go:8) MOVQ 8(SP), AX 0x0032 00050 (main.go:8) MOVQ $1, (AX) 0x0039 00057 (main.go:9) MOVQ AX, "".~r0+32(SP) 0x003e 00062 (main.go:9) MOVQ 16(SP), BP 0x0043 00067 (main.go:9) ADDQ $24, SP 0x0047 00071 (main.go:9) RET 0x0048 00072 (main.go:9) NOP 0x0048 00072 (main.go:6) PCDATA $1, $-1 0x0048 00072 (main.go:6) PCDATA $0, $-2 0x0048 00072 (main.go:6) CALL runtime.morestack_noctxt(SB) 0x004d 00077 (main.go:6) PCDATA $0, $-1 0x004d 00077 (main.go:6) JMP 0 0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 39 48 eH..%....H;a.v9H 0x0010 83 ec 18 48 89 6c 24 10 48 8d 6c 24 10 48 8d 05 ...H.l$.H.l$.H.. 0x0020 00 00 00 00 48 89 04 24 e8 00 00 00 00 48 8b 44 ....H..$.....H.D 0x0030 24 08 48 c7 00 01 00 00 00 48 89 44 24 20 48 8b $.H......H.D$ H. 0x0040 6c 24 10 48 83 c4 18 c3 e8 00 00 00 00 eb b1 l$.H........... rel 5+4 t=17 TLS+0 rel 32+4 t=16 type.int+0 rel 41+4 t=8 runtime.newobject+0 rel 73+4 t=8 runtime.morestack_noctxt+0 "".returnAddr1 STEXT nosplit size=10 args=0x8 locals=0x0 funcid=0x0 0x0000 00000 (main.go:12) TEXT "".returnAddr1(SB), NOSPLIT|ABIInternal, $0-8 0x0000 00000 (main.go:12) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (main.go:12) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (main.go:15) MOVQ $0, "".~r0+8(SP) 0x0009 00009 (main.go:15) RET 0x0000 48 c7 44 24 08 00 00 00 00 c3 H.D$...... "".main STEXT nosplit size=1 args=0x0 locals=0x0 funcid=0x0 0x0000 00000 (main.go:18) TEXT "".main(SB), NOSPLIT|ABIInternal, $0-0 0x0000 00000 (main.go:18) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (main.go:18) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (main.go:20) RET 0x0000 c3 . go.cuinfo.packagename. SDWARFCUINFO dupok size=0 0x0000 6d 61 69 6e main ""..inittask SNOPTRDATA size=24 0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x0010 00 00 00 00 00 00 00 00 ........ go.info."".returnAddr$abstract SDWARFABSFCN dupok size=24 0x0000 04 2e 72 65 74 75 72 6e 41 64 64 72 00 01 01 0c ..returnAddr.... 0x0010 61 00 08 00 00 00 00 00 a....... rel 0+0 t=24 type.*int+0 rel 0+0 t=24 type.int+0 rel 19+4 t=31 go.info.int+0 go.info."".returnAddr1$abstract SDWARFABSFCN dupok size=25 0x0000 04 2e 72 65 74 75 72 6e 41 64 64 72 31 00 01 01 ..returnAddr1... 0x0010 0c 61 00 0e 00 00 00 00 00 .a....... rel 0+0 t=24 type.*int+0 rel 0+0 t=24 type.int+0 rel 20+4 t=31 go.info.*int+0 gclocals·2a5305abe05176240e61b8620e19a815 SRODATA dupok size=9 0x0000 01 00 00 00 01 00 00 00 00 ......... gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 ........
Escape cases
In addition to the return local pointer above, there will also be escape when assigning a value to a reference class member in a reference object
It can be understood that when accessing a reference object, the underlying layer is actually accessed indirectly through a pointer, but if you access the reference members inside again, there will be a second indirect access. In this way, the operation of this part of the object may greatly escape
The reference types of Go are func,interface,slice,map,channel and pointer
Case summary
package main import ( "log" ) func demo(a *int) { return } func demo1(a []string) { return } func main() { /** Escape cases */ //Case 1 //For [] interface {} data type, escape must occur through [] assignment data1 := []interface{}{100, 200} data1[0] = 100 //Case 2 //If the map[string]interface {} type attempts to assign a value, it must escape data2 := make(map[string]interface{}) data2["key"] = 200 //Case 3 //map[interface{}]interface {} type attempts to assign values, which will lead to the assignment of key and value and escape data3 := make(map[interface{}]interface{}) data3[100] = 200 //Case 4 //map[string][]string data type, assignment will cause escape data4 := make(map[string][]string) data4["key"] = []string{"value"} //Case 5 //[] * int data type, the assigned right value will escape a := 10 data5 := []*int{nil} data5[0] = &a //Case 6 //func(*int) function type. If the function is assigned, the passed formal parameters will escape data6 := 10 f := demo f(&data6) log.Println(data6) //Case 7 //func([]string): function type. Assigning [] string{"value"} will make the passed parameters escape s := []string{"aceld"} demo1(s) log.Println(s) //Case 8 //Channel [] string data type. If [] string{"value"} is transmitted in the current channel, escape will occur ch := make(chan []string) s1 := []string{"aceld"} go func() { ch <- s1 }() //Case 9 //Function returns a local pointer and an escape occurs func() *int { a := 1 return &a }() }
Results of performing escape analysis
$ go tool compile -m main.go main.go:7:6: can inline demo main.go:11:6: can inline demo1 main.go:49:3: inlining call to demo main.go:55:7: inlining call to demo1 main.go:62:5: can inline main.func1 main.go:7:11: a does not escape main.go:11:12: a does not escape main.go:41:2: moved to heap: a main.go:21:24: []interface {}{...} does not escape main.go:21:25: 100 does not escape main.go:21:30: 200 does not escape main.go:22:11: 100 escapes to heap main.go:26:15: make(map[string]interface {}) does not escape main.go:27:15: 200 escapes to heap main.go:31:15: make(map[interface {}]interface {}) does not escape main.go:32:7: 100 escapes to heap main.go:32:13: 200 escapes to heap main.go:36:15: make(map[string][]string) does not escape main.go:37:25: []string{...} escapes to heap main.go:42:17: []*int{...} does not escape main.go:50:13: ... argument does not escape main.go:50:13: data6 escapes to heap main.go:54:15: []string{...} escapes to heap main.go:56:13: ... argument does not escape main.go:56:13: s escapes to heap main.go:61:16: []string{...} escapes to heap main.go:62:5: func literal escapes to heap
Escape case I
For [] interface {} data type, escape must occur through [] assignment
package main func main() { data := []interface{}{100, 200} data[0] = 100//escape }
Escape case II
If the map[string]interface {} type attempts to assign a value, it must escape
package main func main() { data := make(map[string]interface{}) data["key"] = 200//escape }
Escape case III
map[interface{}]interface {} type attempts to assign values, which will lead to the assignment of key and value and escape
package main func main() { data := make(map[interface{}]interface{}) data[100] = 200//Both 100 and 200 escaped }
Escape case 4
map[string][]string data type, assignment will cause escape
package main func main() { data := make(map[string][]string)//Will escape to the pile data["key"] = []string{"value"} }
Escape case 5
[] * int data type, the assigned right value will escape
package main func main() { a := 10//escape data := []*int{nil} data[0] = &a }
Escape case 6
func(*int) function type. If the function is assigned, the passed formal parameters will escape
package main import "fmt" func foo(a *int) { return } func main() { data := 10//escape f := foo f(&data) fmt.Println(data) }
Escape case 7
func([]string): function type. Assigning [] string{"value"} will make the passed parameters escape
package main import "fmt" func foo(a []string) { return } func main() { s := []string{"aceld"}//escape foo(s) fmt.Println(s) }
Escape case 8
Channel [] string data type. If [] string{"value"} is transmitted in the current channel, escape will occur
package main func main() { ch := make(chan []string) s := []string{"aceld"}//escape go func() { ch <- s }() }
summary
In other words, the local variables in the function, whether dynamically new or not, will be allocated to the stack or heap, which is determined by the compiler after escape analysis
Generally, externally referenced variables will be put into heap memory. If the compiler is not sure whether they will be externally referenced, they will also be put into heap memory
If you assign a value to a reference class member in a reference object, you will also escape