Skip to content

Latest commit

 

History

History
96 lines (76 loc) · 3.83 KB

File metadata and controls

96 lines (76 loc) · 3.83 KB

About escape analysis

Escape analysis is an important task for GO compiler, it determines where a variable is constructed at compilation time (stack or heap)?

Escape analysis is regulated based on the escape analysis algorithm. It varies from language to language. In short:

If the compiler CAN NOT figure out if a value will be referenced after it returns from a function, in order to maintain the program's integrity, it moves the value to the heap. Otherwise, it will be constructed in the program's stack frame.

Let's see an example.

package main

type hero struct {
	name 		string
	superpower	string
}

//go:noinline
func createSuperMan() hero {
	h := hero {
		name: 		"Superman",
		superpower: "X-ray vision",
	}
	println("Superman ", &h)
	return h
}

//go:noinline
func createTheFlash() *hero {
	h := hero {
		name: 		"The Flash",
		superpower: "Super speed",
	}
	println("The Flash ", &h)
	return &h
}

/*
	This example use go:noinline (no space between // and go) to prevent the compiler from inlining the code
	for these functions directly in main. Inlining would erase the function calls and complicate the example.
*/
func main() {
	h1 := createSuperMan()

	// h will escape to heap, after createTheFlash returns its value, no matter if h2 is declared or not
	h2 := createTheFlash()

	println("Superman ", &h1, "The Flash", &h2)
}

The above example is a mimicry of the example code in William Kennedy's post about escape analysis. I just replace the user struct with my hero struct. The main() function creates two heroes: Superman and The Flash.

When we create Superman (see createSuperMan()), we first create a hero struct h and return its value copy to the main function. h stays in the stack frame. When it returns to the main function, the value of h is still kept in the stack but is no longer valid to access. The memory block will be re-framed and re-initialised after the next function call.

When we create The Flash (see createTheFlash()), we first create a hero struct h and return its reference (i.e. the address) to the main function. In this case, h is 'shared' with the main function. Thus, the compiler considers 'that sharing' can potentially hurt the program integrity and moves h to heap. If we run with GC flags -gcflags "-m -m", we can see some information about variable escapes to heap:

$ go run -gcflags "-m -m" escape_analysis.go
./escape_analysis.go:9:6: cannot inline createSuperMan: marked go:noinline
./escape_analysis.go:19:6: cannot inline createTheFlash: marked go:noinline
./escape_analysis.go:32:6: cannot inline main: function too complex: cost 133 exceeds budget 80
./escape_analysis.go:14:23: createSuperMan &h does not escape
./escape_analysis.go:25:9: &h escapes to heap
./escape_analysis.go:25:9:      from ~r0 (return) at ./escape_analysis.go:25:2
./escape_analysis.go:20:2: moved to heap: h
./escape_analysis.go:24:24: createTheFlash &h does not escape
./escape_analysis.go:38:23: main &h1 does not escape
./escape_analysis.go:38:41: main &h2 does not escape
Superman  0xc000046710
The Flash  0xc00000c040
Superman  0xc000046768 The Flash 0xc000046760

Because &h escapes to heap, h thus will be constructed in heap and both main function and the two hero creation functions can ONLY INDIRECTLY ACCESS to it.

REMEMBER! Escape analysis only happens at the compilation time, thus only GO compiler knows if a variable should be constructed in stack or heap. Although keeping variables in stack can avoid latency introduced by GC that operates on heap,
we should not be obsessive about figuring out where a variable puts when we are coding. Instead, ensure our program is coded correctly (from the perspective of user features) and has good readability and interpretability should always be our primary goal.