Рубрики
Uncategorized

Я собираюсь быть в стопке. Нет, ты должен быть в этой куче.

Автор оригинала: David Wong.

Адрес: Я хочу быть в стопке. Нет, ты должен быть в куче.

Предисловие

Когда мы пишем код, мы иногда задаемся вопросом, где находится эта переменная. В этот момент кто-то может сказать: в стопке, в куче. Поверь мне, ты прав.

Но в результате у вас все еще есть наполовину испеченная идея. Это нехорошо. На случай, если тебя оглушат. Сегодня давайте раскроем секрет Go в этой области и приложим собственные усилия, чтобы обогатить нашу одежду и еду.

проблема

type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo() *User {
    return &User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"}
}

func main() {
    _ = GetUserInfo()
}

Начало-это знак вопроса, обучение с помощью вопросов. Пожалуйста, позвоните в main GetUserInfo Позже возвращается &Пользователь{...} 。 Выделяется ли эта переменная в стек или в кучу?

Что такое куча/стек

Я не собираюсь подробно останавливаться здесь на стеке. Я просто дам вам краткое введение в основы этой статьи. Следующим образом:

  • Куча: Вообще говоря, она управляется вручную, применяется, распространяется и выпускается вручную. Как правило, объем используемой памяти является переменным, и обычно хранятся большие объекты. Кроме того, его распределение происходит относительно медленно и включает в себя относительно большое количество командных действий.
  • Стек: Компилятор автоматически управляет приложением, распределением и выпуском. Обычно он не слишком большой. Наши общие параметры функций (разные платформы позволяют использовать разные объемы памяти), локальные переменные и так далее хранятся в стеке.

Сегодня мы представляем язык Go, его распределение стека анализируется компилятором, управляется GC, и его действие по анализу и выбору находится в центре сегодняшнего обсуждения.

Что такое Анализ побега

В теории оптимизации компилятора анализ escape-это метод определения динамического диапазона указателя. Проще говоря, это анализ того, где в программе можно получить доступ к указателю.

Вообще говоря, анализ экранирования заключается в определении того, следует ли помещать переменную в стек или в стек. Правила заключаются в следующем:

  1. Есть ли какие-либо другие (нелокальные) ссылки? до тех пор, пока Будьте на картах Если цитируется, то это Определенные Выделите в кучу. В противном случае он выделяется в стек.
  2. Даже если на него нет внешней ссылки, объект слишком велик для хранения в стеке. Все еще возможно выделить его в кучу

Как вы можете понять, анализ экранирования-это поведение, которое компилятор использует для определения того, выделяются ли переменные в стеке или в стеке.

На каком этапе следует установить побег?

Установите escape во время компиляции, обратите внимание, что он не выполняется во время выполнения

Почему нам нужно бежать?

Мы можем изменить это и подумать о том, что произошло бы, если бы переменные были распределены в кучу. Например:

  • Давление сбора мусора (GC) возрастает
  • Системные издержки, связанные с запросом, выделением и освобождением памяти, увеличиваются (относительно стека)
  • Динамическое выделение приводит к определенной фрагментации памяти

На самом деле, в целом, частые приложения и выделение памяти кучи имеют определенную “стоимость”. Это косвенно повлияет на эффективность работы приложения и всей системы в целом. Таким образом, это правильный способ управления-распределять ресурсы в соответствии с потребностями и максимально гибко использовать ресурсы. Вот почему необходим анализ побега, вам не кажется?

Как определить, стоит ли бежать

Во-первых, с помощью команд компилятора вы можете увидеть подробный процесс анализа побега. И набор инструкций --cflags Используется для передачи параметров идентификации компилятору Go, что включает в себя следующее:

  • Он выводит стратегию оптимизации для анализа побега. На самом деле, в общей сложности можно использовать четыре. -m Но есть большой объем информации. Обычно одного достаточно.
  • -l Функция встраивания отключена, где встроенная функция отключена для лучшего наблюдения за побегом и уменьшения помех
$ go build -gcflags '-m -l' main.go

Во-вторых, просмотрите его, выполнив декомпиляцию команд

$ go tool compile -S main.go

Примечание: Через go инструмент компиляции -справка Просмотр всех параметров идентификации, которые разрешено передавать компилятору

Случаи побега

Случай 1: Указатель

Первый случай-это вопрос, который был задан в самом начале. Теперь посмотрите на это еще раз и подумайте об этом следующим образом:

type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo() *User {
    return &User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"}
}

func main() {
    _ = GetUserInfo()
}

Взгляните на выполнение заказа следующим образом:

$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:10:54: &User literal escapes to heap

Посмотрев на результаты анализа, мы можем увидеть, что &Пользователь Экранируется в кучу, то есть выделяется в кучу. Разве это проблема? Посмотрите на код сборки еще раз, чтобы подтвердить, следующим образом:

$ go tool compile -S main.go                
"".GetUserInfo STEXT size=190 args=0x8 locals=0x18
    0x0000 00000 (main.go:9)    TEXT    "".GetUserInfo(SB), $24-8
    ...
    0x0028 00040 (main.go:10)    MOVQ    AX, (SP)
    0x002c 00044 (main.go:10)    CALL    runtime.newobject(SB)
    0x0031 00049 (main.go:10)    PCDATA    $2, $1
    0x0031 00049 (main.go:10)    MOVQ    8(SP), AX
    0x0036 00054 (main.go:10)    MOVQ    $13746731, (AX)
    0x003d 00061 (main.go:10)    MOVQ    $7, 16(AX)
    0x0045 00069 (main.go:10)    PCDATA    $2, $-2
    0x0045 00069 (main.go:10)    PCDATA    $0, $-2
    0x0045 00069 (main.go:10)    CMPL    runtime.writeBarrier(SB), $0
    0x004c 00076 (main.go:10)    JNE    156
    0x004e 00078 (main.go:10)    LEAQ    go.string."EDDYCJY"(SB), CX
    ...

Мы сосредоточились на инструкциях по вызову и обнаружили, что они были выполнены. время выполнения.новый объект

Результаты анализа

это потому, что GetUserInfo() Return ссылается на объект, и ссылка возвращается вне метода. Таким образом, компилятор назначает объект куче, а не стеку. В противном случае после завершения метода локальные переменные будут восстановлены, это не опрокидывание. Поэтому естественно в конечном итоге распределить его в кучу.

Подумай еще раз

Таким образом, вы можете подумать, что все объекты-указатели должны находиться в куче? Нет. Следующим образом:

func main() {
    str := new(string)
    *str = "EDDYCJY"
}

Как вы думаете, где будет размещен этот объект? Следующим образом:

$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:4:12: main new(string) does not escape

Очевидно, что этот объект выделен для стека. Основной момент заключается в том, что на него не ссылаются за пределами области, которая остается там. главная Так что оно не ускользнуло.

Случай 2: Неопределенный тип

func main() {
    str := new(string)
    *str = "EDDYCJY"

    fmt.Println(str)
}

Взгляните на выполнение заказа следующим образом:

$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:9:13: str escapes to heap
./main.go:6:12: new(string) escapes to heap
./main.go:9:13: main ... argument does not escape

Взглянув на результаты анализа, мы можем увидеть, что str Переменные экранируются в кучу, то есть объект выделяется в куче. Но в последнем случае он все еще был в стопке, и мы тоже. fmt Выведите его. Что, черт возьми, с этим случилось?

Результаты анализа

По сравнению с первым случаем, второй случай добавляет только одну строку кода fmt. Принтлн(str) Проблема, должно быть, в этом. Его прототип:

func Println(a ...interface{}) (n int, err error)

Благодаря анализу мы можем узнать, что когда параметр формы равен интерфейс На этапе компиляции компилятор не может определить его конкретный тип. Таким образом, происходит экранирование и в конечном итоге выделяется в кучу.

Если вы заинтересованы в отслеживании исходного кода, загляните внутрь. поразмышляйте. Тип(arg).Kind() Оператор, который вызывает экранирование кучи, и представление интерфейс Тип вызывает выделение объекта в кучу

Случай 3. Параметры утечки

type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo(u *User) *User {
    return u
}

func main() {
    _ = GetUserInfo(&User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"})
}

Взгляните на выполнение заказа следующим образом:

$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:9:18: leaking param: u to result ~r1 level=0
./main.go:14:63: main &User literal does not escape

Мы отмечаем, что утечка параметров Представление, которое описывает переменные u Это параметр утечки. В сочетании с кодом вы можете узнать его передачу GetUserInfo После метода переменная возвращается напрямую без какой-либо ссылки или другого действия, связанного с переменной. Таким образом, эта переменная на самом деле не экранируется, она все еще находится в области видимости. главная() В, поэтому он выделяется в стеке

Подумай еще раз

Тогда как вы можете выделить его в кучу? Объедините первый случай с третьим. Изменить следующим образом:

type User struct {
    ID     int64
    Name   string
    Avatar string
}

func GetUserInfo(u User) *User {
    return &u
}

func main() {
    _ = GetUserInfo(User{ID: 13746731, Name: "EDDYCJY", Avatar: "https://avatars0.githubusercontent.com/u/13746731"})
}

Взгляните на выполнение заказа следующим образом:

$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:10:9: &u escapes to heap
./main.go:9:18: moved to heap: u

С незначительным изменением он считает, что на него будут ссылаться извне, поэтому он правильно распределен в куче.

резюме

В этой статье я познакомлю вас с концепциями и правилами анализа побега и приведу несколько примеров, чтобы углубить ваше понимание. Но реальность, безусловно, гораздо больше, чем эти случаи. Что вам нужно сделать, так это овладеть этим методом. Если вы столкнетесь с этим, просто посмотрите на это еще раз. Кроме того, вам необходимо обратить внимание на:

  • Статическое распределение в стеке, производительность должна быть лучше, чем динамическое распределение в стеке
  • Нижняя часть выделяется для кучи или стека. На самом деле, это прозрачно для вас и не требует чрезмерного беспокойства.
  • Анализ Escape варьируется от версии к версии Go (изменения, оптимизации)
  • Прямой проход перейти к сборке-gcflags '-m -l' Вы можете увидеть процесс и результаты анализа escape.
  • Это не обязательно лучший способ использовать указатели везде. Вам нужно использовать правильный.

Я уже думал о том, чтобы написать об анализе побега раньше. До недавнего времени, когда кто-то спрашивал меня во время ночного чтения, писать все равно было необходимо. За эту часть знаний. Мое предложение состоит в том, чтобы понять это правильно, но нет необходимости запоминать это. В зависимости от базовых знаний и наблюдения за отладкой команд. Как сказал Цао Да ранее: “Вы думаете о половине дня анализа побега, измерении давления, узкое место заблокировано”, нет необходимости уделять этому слишком много внимания.

Справочные ресурсы

  • Анализ побега Голанга
  • ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ
  • анализ побега

Оригинал: “https://developpaper.com/im-going-to-be-on-the-stack-no-you-should-be-on-the-pile/”