Question:
Does a function such as fmt.Printf cause arguments passed in to escape from the stack to the heap at compile time?
Golang’s issue :(github.com/golang/go/i…
Knife sharpening (escape analysis tool) :
Analysis tools:
- 1. Use the compile tool to view the detailed escape analysis process (
go build -gcflags '-m -l' main.go
) - 2. Run the decompilation command
go tool compile -S main.go
The compiler parameter (-gcflags) is described:
-
-n: disables compilation optimization
-
-l: Disables inlining (can effectively reduce program size)
-
-M: Escape analysis (repeat up to four times)
-
-benchmem: displays memory allocation statistics during pressure measurement
Training (experiment) :
Example:
package main
import (
"fmt"
"runtime"
)
type obj struct{}
func main(a) {
a := &obj{}
fmt.Printf("%p\n", a)
b := &obj{}
println(b)
}
Copy the code
Escape analysis:
./main.go:17:7: &obj literal escapes to heap
./main.go:18:12:... argument does not escape ./main.go:20:7: &obj literal does not escape
0x11a6c10
0xc000072f1f
Copy the code
And you can see that our variable A escaped onto the heap because of the function of FMT.
War (trying to verify the problem) :
First of all, we only verified the occurrence of this problem, but did not solve it. Students with ideas can directly submit to M
The current online interpretation goes in two directions:
Functions such as fmt.printf cause arguments passed in to escape from the stack to the heap at compile time
The first criminal:
The second argument to FMT.Printf is an interface type that uses an assertion in the underlying call logic:
Printf->Fprintf->doPrintf->reflect.TypeOf(arg).Kind()
Copy the code
Here some preliminary conclusions obtained by simulation FMT package (reusee. Making. IO/post/escape…
Therefore, we can assume that during compilation, the compiler cannot determine the specific type of A. The interface{} type would normally be reflected at the bottom, but the reflation.typeof (arg).kind () gets the underlying data TypeOf an interface type object, and the heap escape occurs. The result is that an escape occurs when the input parameter is of an empty interface type.
But it is not true that passing a value to func(interface{}), or a pointer to func(*struct), will cause escape analysis. It’s just that in most cases, reflection is used internally, causing escape (switch type does not cause escape).
Validation:
package main
import (
"fmt"
"runtime"
)
type obj struct{}
func main(a) {
a := &obj{}
fmt.Printf("%p\n", a)
b := &obj{}
reflect.TypeOf(b).Kind()
println(b)
}
Copy the code
Escape analysis:
# command-line-arguments
./main.go:20:7: &obj literal escapes to heap
./main.go:21:12:... argument does not escape ./main.go:23:7: &obj literal escapes to heap
0x11a6c30
0x11a6c30
Copy the code
Two variables can be found on the heap, as to why the same address, can focus on my another article: www.jianshu.com/p/e0fd84a59…
Second offender:
Printf->Fprintf->doPrintf->printArg ->doPrintf-> doPrintf->printArg
We find that the assignment code we pass in is assigned to a member variable of the pp pointer:
func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}
...
}
Copy the code
The pointer p of pp type is returned by the constructor newPrinter, so its life cycle is changed. P must have escaped, and P references the incoming pointer U, which was tested to escape.
validation
package main
import (
"fmt"
)
type obj struct{}
type pointer struct {
o *obj
}
func main(a) {
a := &obj{}
fmt.Printf("%p\n", a)
b := &obj{}
p := newPrinter()
p.o = b
println(b)
}
func newPrinter(a) *pointer {
return new(pointer)
}
Copy the code
Conclusion:
# command-line-arguments
./main.go:23:12: new(pointer) escapes to heap
./main.go:14:7: &obj literal escapes to heap // a
./main.go:15:12:... argument does not escape ./main.go:16:7: &obj literal escapes to heap // b
0x11a6c10
0x11a6c10
Copy the code
We see that b, which is referenced by P, is also pushed onto the heap
Work received (Summary) :
Functions such as FMT.Printf pass arguments and heap escape.
Although go handles pre-compile allocations for daily development and we don’t have to pay attention to stack usage, consciously avoiding heap escape can significantly improve the performance of a heavy service. Heap escape is not uncommon in GO, and the performance cost of the impact on GC is significant.
Come across frequently on a daily basis:
1. The function returns a pointer to an object on the stack, or a parameter leak, which extends the life cycle of the pointer object.
2. Call reflection (unknown type) (the first problem in the FMT case).
3. A pointer referenced by a variable that has escaped must escape (the second problem in the FMT case).
4. Pointers referenced by pointer types slice, map, and chan must escape.
Benefits of avoiding escape:
-
1. Reduce gc pressure, non-escaping objects are allocated on the stack, and resources are reclaimed when the function returns, without gc tag cleanup
-
2. After escape analysis, it can determine which variables can be allocated on the stack, which can be allocated faster than the heap, with better performance (less system overhead)
-
3. Reduce memory fragmentation caused by dynamic allocation
Reflection:
Is passing a pointer really more efficient than passing a value?
We know that passing Pointers reduces the copying of underlying values, improves efficiency and is less burdensome
However, when data is small and large, passing Pointers is not necessarily efficient because pointer passing often results in escapes to the heap, adding to the BURDEN of the GC.
Example:
Chan using a pointer is 30% slower than chan using a value. Chan using a pointer escapes and gc slows down.
Ps: stackoverflow.com/questions/4…