GO - анализ побега
Во многих языках локальные переменные находятся в стеке вызовов
В JavaScript/Python только переменные замыкания находятся в куче, потому что они должны находиться за пределами вызовов функций, они создаются.
В GO некоторые типы GO (например, тип слайса []int
) ссылаться на другие части памяти, такие как JavaScript/Python.
В GO не все типы переменных содержат ссылки, такие как Javascript/Python.
Например,
1) [3]int
переменная типа b
напрямую хранит массив int
Как и C, за исключением того, что C позволяет получить доступ к каждому местоположению элемента массива с использованием синтаксиса C &b[index]
, для большего контроля
2) int
переменная типа c
напрямую хранит int
значение, как C, за исключением того, что C дает больше контроля, предоставляя синтаксис (&c
), чтобы получить доступ к местоположению.
В GO я понимаю, что локальные переменные в куче / стеке зависят от применения escape-анализа компилятора в примере кода (ниже),
func foo() []int {
// the array lives beyond the call to foo in which it is created
var a [5]int
return a[:] // range operator
}
который сообщает компилятору эту переменную a
живет за ее пределами, поэтому размещать в куче, а не в стеке.
Вопрос:
Переменная a
выделяться в кучу?
1 ответ
В Go вы должны доверять компилятору для принятия наилучшего решения. Он будет выделять память в стеке, если это возможно. Смотрите также FAQ:
С точки зрения правильности вам не нужно знать. Каждая переменная в Go существует до тех пор, пока есть ссылки на нее. Место хранения, выбранное реализацией, не имеет отношения к семантике языка.
Место хранения влияет на написание эффективных программ. Когда это возможно, компиляторы Go будут размещать переменные, которые являются локальными для функции в кадре стека этой функции. Однако, если компилятор не может доказать, что на переменную не ссылаются после возврата из функции, компилятор должен выделить переменную в куче для сбора мусора, чтобы избежать ошибок висячих указателей. Кроме того, если локальная переменная очень велика, имеет смысл хранить ее в куче, а не в стеке.
В текущих компиляторах, если переменная имеет свой адрес, эта переменная является кандидатом для размещения в куче. Однако базовый анализ escape распознает некоторые случаи, когда такие переменные не будут жить после возврата из функции и могут находиться в стеке.
Без оптимизации (встраивание) да a
будет выделяться в куче. Мы можем проверить анализ побега, передав -gcflags='-m'
( https://play.golang.org/p/l3cZFK5QHO):
$ nl -ba 1.go
1 package main
2
3 func inlined() []int {
4 var a [5]int
5 return a[:]
6 }
7
8 //go:noinline
9 func no_inline() []int {
10 var b [5]int
11 return b[:]
12 }
13
14 func main() {
15 var local_array [5]int
16 var local_var int
17 println(no_inline())
18 println(inlined())
19 println(local_array[:])
20 println(&local_var)
21 }
$ go build -gcflags='-m' 1.go
# command-line-arguments
./1.go:3: can inline inlined
./1.go:18: inlining call to inlined
./1.go:5: a escapes to heap
./1.go:4: moved to heap: a
./1.go:11: b escapes to heap
./1.go:10: moved to heap: b
./1.go:18: main a does not escape
./1.go:19: main local_array does not escape
./1.go:20: main &local_var does not escape
Видим, что компилятор решил выделить inlined.a
в строке 5 и no_inline.b
в строке 10 в куче, потому что они оба выходят из своей сферы видимости.
Однако после включения компилятор заметил, что a
больше не экранирует, поэтому определяет, что переменная может быть снова размещена в стеке (строка 18).
В результате переменная a
распределяется на main
стек рутин, а переменная b
выделяется в куче. Как видно из вывода, адрес b
находится на 0x1043xxxx, в то время как все остальные на 0x1042xxxx.
$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40