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 commandgo 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…