Автор оригинала: 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-это метод определения динамического диапазона указателя. Проще говоря, это анализ того, где в программе можно получить доступ к указателю.
Вообще говоря, анализ экранирования заключается в определении того, следует ли помещать переменную в стек или в стек. Правила заключаются в следующем:
- Есть ли какие-либо другие (нелокальные) ссылки? до тех пор, пока Будьте на картах Если цитируется, то это Определенные Выделите в кучу. В противном случае он выделяется в стек.
- Даже если на него нет внешней ссылки, объект слишком велик для хранения в стеке. Все еще возможно выделить его в кучу
Как вы можете понять, анализ экранирования-это поведение, которое компилятор использует для определения того, выделяются ли переменные в стеке или в стеке.
На каком этапе следует установить побег?
Установите 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/”