Хотя есть несколько отличных статей о # escape-analysis для #go, этот пост - моя попытка демистифицировать и представить тему как можно проще.
Я буду запускать несколько сценариев и раскрывать информацию анализа выхода компилятора, используя следующую команду:
go build -gcflags “-m -l”
Эти сообщения состоят из двух частей:
- Часть 1: вы уже на этой странице
- Часть 2: ‹link›
Каждый сценарий отформатирован следующим образом:
// ** Scenario #** // // change: <brief text to describe the scenario> // program that I ran ... // escape analysis output ... // explanation ... // ** Scenario 1** // // not using any pointers // program that I ran package del type S struct { x int } func f1() { x:= S{1} _ = f2(x) } func f2(x S) S { y := x return y } // escape analysis output <blank> // explanation Since there are no pointers/references all variables are local to the function stack. No heap allocation required // ** Scenario 2 ** // // change: returning a pointer to a struct created in the called function // program that I ran package del type S struct { x int } func f1() { x:= S{1} _ = f2(x) } func f2(x S) *S { y := x return &y } /* // escape analysis output # del .\del.go:13:2: moved to heap: y // explanation In scenario 2, “y” was moved to heap by the compiler because f2 is returning the reference to y. And upon return stack of f2 will be marked invalid. Thus for f1 to use the reference to y, y must be available even after f2 has finished. // ** Scenario 3 ** // // change: returning a struct created in the called function using a pointer to a struct in the calling function // program that I ran package del type S struct { x int } func f1() { s:= S{1} _ = f2(&s) } func f2(x *S) S { y := *x return y } /* // escape analysis output # del .\del.go:12:9: f2 x does not escape // explanation Here, x does not escape and stays on f1's stack. This is because while f2 is running f1's stack will still be available */ // ** Scenario 4** // // change: returning a pointer to the struct created in the called function using a pointer to a struct in the calling function // program that I ran package del type S struct { x int } func f1() { s:= S{1} _ = *f2(&s) _ = *f3(&s) } func f2(x *S) *S { y := x return y } func f3(x *S) *S { y := *x // line 18 return &y } /* // escape analysis output # del .\del.go:13:9: leaking param: x to result ~r1 level=0 .\del.go:17:9: f3 x does not escape .\del.go:18:4: moved to heap: y // explanation We have 2 functions f2 and f3, that achieve the same thing i.e. accept a *S and return a *S. In f2, y and x are both references to the stack address in f1. While in f3, y contains the value of x and lives on f3's stack. Now since f1 dereferences what f3 returns, y needs to be available on the heap for f1 to deference. */ // ** Scenario 5 ** // // change: constructing the struct in the called functions // program that I ran package del type S struct { x int } func f1() { i := 123 _ = *f2(i) _ = f3(&i) _ = *f4(&i) } func f2(x int) *S { y := S{x} // line 16 return &y } func f3(x *int) S { y := S{*x} return y } func f4(x *int) *S { y := S{*x} // line 24 return &y } /* // escape analysis output # del .\del.go:16:4: moved to heap: y .\del.go:19:9: f3 x does not escape .\del.go:23:9: f4 x does not escape .\del.go:24:4: moved to heap: y // explanation Shouldn't be a surprise for why y in f2 and f4 is moved to heap */ // ** Scenario 6 ** // // change: S.x is not *int type // program that I ran package del type S struct { x *int } func f1() { i := 123 _ = *f2(i) _ = f3(&i) _ = *f4(&i) } func f2(x int) *S { y := S{&x} // line 16 return &y } func f3(x *int) S { y := S{x} // line 20 return y } func f4(x *int) *S { y := S{x} // line 24 return &y } /* // escape analysis output # del .\del.go:15:9: moved to heap: x .\del.go:16:4: moved to heap: y .\del.go:19:9: leaking param: x to result ~r1 level=0 .\del.go:23:9: leaking param: x .\del.go:24:4: moved to heap: y .\del.go:10:4: moved to heap: i // explanation For f2, it should be clear why y is being moved to heap. x is also moved to heap for y to continue referencing it even after f2 returns For f3, y is being returned to f1 where x already exists. So no need to place anything on heap For f4, it should be cleat why y is being moved to heap. i (=x since same reference) is also moved to heap for y to continue referencing it even after f1 returns */ // ** Scenario 7 ** // // change: function assigns value to a member of the struct, and returns nothing // program that I ran package del type S struct { x *int } func f1() { i := 123 var s S f2(i, s) f3(&i, s) f4(i, &s) f5(&i, &s) } func f2(x int, y S) { y.x = &x // line 18 } func f3(x *int, y S) { y.x = x // line 21 } func f4(x int, y *S) { y.x = &x // line 22 x } func f5(x *int, y *S) { y.x = x // line 26 x } /* // escape analysis output # del .\del.go:17:16: f2 y does not escape .\del.go:20:9: f3 x does not escape .\del.go:20:17: f3 y does not escape .\del.go:23:9: moved to heap: x .\del.go:23:16: f4 y does not escape .\del.go:26:9: leaking param: x .\del.go:26:17: f5 y does not escape .\del.go:10:4: moved to heap: i // explanation For f2, nothing is moved to heap since everything lives on the stack of f2 and isn't required once f2 returns For f3, nothing is moved to heap since everything lives on the stack of f3 and isn't required once f3 returns For f4, y is coming from another stack and for x to be available after f4 returns, x must be moved to the heap For f5, same explanation as f4, x must be moved to heap except that x here refers to i since both are pointers to same address. Hence, i must be moved to heap */
Далее: Часть 2 будет посвящена работе со срезами и картами.
Домашнее задание для читателей:
В. Что такое карты на самом деле?
A. Проверьте это: https://golang.org/src/runtime/map.go#L114
В. Что такое срезы?
A. Проверьте это: https://golang.org/pkg/reflect/#SliceHeader