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
Другие вопросы по тегам