Пример анализа странных явлений
Оригинальный почтовый адрес: https://gocn.io/question/1852
package main
import (
"fmt"
)
func main(){
s := []byte("")
s1 := append(s, 'a')
s2 := append(s, 'b')
// 如果有此行,打印的结果是 a b,否则打印的结果是b b
// fmt.Println(s1, "===", s2)
fmt.Println(string(s1), string(s2))
}
Странное явление: если есть код на линии 14, результат, напечатанный на линии 15a b
, иначе напечатанный результатb b
, версия go, проанализированная в этой статье:
$ go version
go version go1.9.2 darwin/amd64
первоначальный анализ
В первую очередь разбираем, почему в напечатанном результате отсутствует 14 строкаb b
, эта проблема относительно проста, если вы знакомы сslice
Принцип реализации, кратко проанализируемappend
Можно сделать вывод о принципе реализации.
анализ структуры среза
Если вы знакомы с принципом среза, вы можете пропустить эту главу.
Во-первых, краткое понимание структуры срезаопределение структуры slice
соответствующийruntime
См. соответствующий исходный код пакета. См.: https://golang.org/src/runtime/slice.go.
type slice struct {
array unsafe.Pointer
len int
cap int
}
var slice []int
Внутренняя структура определяемой переменной следующая:
slice.array = nil
slice.len = 0
slice.cap = 0
Если мы объявим переменнуюslice := []int{}
илиslice := make([]int, 0)
Внутренняя структура выглядит следующим образом:
slice.array = 0xxxxxxxx // 分配了地址
slice.len = 0
slice.cap = 18208800
При использованииmake([]byte, 5)
Если определено, структура выглядит следующим образом:
При использованииs := s[2:4]
, Структура, как показано ниже:
после анализаslice
Реализация отражения de:Go Slices: usage and internals, также можно проанализировать в программе.slice
Соответствующая структура в отражении
// slice 对应的结构体
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
// string 对应结构体
type StringHeader struct {
Data uintptr
Len int
}
Следующие функции могут быть получены непосредственноslice
Базовый указатель:
func bytePointer(b []byte) unsafe.Pointer {
// slice 的指针本质是*reflect.SliceHeader
p := (*reflect.SliceHeader)(unsafe.Pointer(&b))
return unsafe.Pointer(p.Data)
}
Приложение Принцип реализации
Добавить псевдокод реализации, код уже поддерживает его по умолчаниюslice
заnil
Случай
func Append(slice, data []byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
copy(slice[l:], data)
return slice
}
append
Прототип функции выглядит следующим образом, где T — универсальный тип.
func append(s []T, x ...T) []T
Развернуть анализ
Чтобы облегчить анализ программы, мы добавляем в программу информацию о печати.Код и результаты выглядят следующим образом:
package main
import (
"fmt"
)
func main() {
s := []byte("")
println(s) // 添加用于打印信息, println() print() 为go内置函数,直接输出到 stderr 无缓存
s1 := append(s, 'a')
s2 := append(s, 'b')
// fmt.Println(s1, "===", s2)
fmt.Println(string(s1), string(s2))
}
Результат работы программы следующий:
$ go run q.go
[0/32]0xc420045ef8
b b
После запуска результатаs := []byte("")
После инициализации внутренняя структура выглядит следующим образом:
s.len = 0
s.cap = 32
s.ptr = 0xc420045ef8
Давайте проанализируем, что происходит, когда вызываются следующие две строки кода:
s1 := append(s, 'a')
s2 := append(s, 'b')
s1 := append(s, 'a')
Анализ кодовых вызовов:
// slice = s data = `a` slice.len = 0 slice.cap = 32
func Append(slice, data []byte) []byte {
l := len(slice) // l = 0
// l = 0 len(data) = 1 cap(slice) = 32 1 + 1 > 32 false
if l + len(data) > cap(slice) {
newSlice := make([]byte, (l+len(data))*2)
copy(newSlice, slice)
slice = newSlice
}
// l = 0 len(data) = 1
slice = slice[0:l+len(data)] // slice = slice[0:1]
copy(slice[l:], data) // 调用变成: copy(slice[0:], 'a')
return slice // 由于未涉及到重分配,因此返回的还是原来的 slice 对象
}
s2 := append(s, 'b')
Анализ точно такой же.
упрощатьapend
Путь обработки функции, когда нетslice
В случае перераспределения памяти разверните анализ напрямую:
s1 := append(s, 'a')
s2 := append(s, 'b')
Эквивалентно
s1 := copy(s[0:], 'a')
s2 := copy(s[0:], 'b') // 直接覆盖了上的赋值
Основываясь на приведенном выше анализе, вывод кода можно хорошо объяснить.b b
Случай. Но как избежать такой ситуации? Проблема в этом утверждении
s := []byte("")
Выполнение заявленияs.len = 0 s.cap = 32
, приводит кappend
Работа не работает должным образом, так как его использовать нормально? просто поставьs.len = s.cap = 0
приведет кslice
существуетappend
Этого можно избежать путем перераспределения в .
Правильное написание должно быть:
func main() {
// Notice []byte("") -> []byte{} 或者 var s []byte
s := []byte{}
s1 := append(s, 'a')
s2 := append(s, 'b')
// fmt.Println(s1, "===", s2)
fmt.Println(string(s1), string(s2))
}
Как видно, хорошие привычки программирования могут избежать многих проблем, озадачивающих расследование.
Углубленный анализ
Итак, поскольку ошибка появилась вs := []byte("")
В этом предложении, то почему это предложение приводит кs.cap = 32
Шерстяная ткань? Какова логика этого утверждения?
s := []byte("")
Эквивалентно следующему коду:
// 初始化字符串
str := ""
// 将字符串转换成 []byte
s := []byte(str)
на языке гоs := []byte(str)
Основополагающий факт называетсяstringtoslicebyte
Реализована, эта функция есть в GOruntime
в сумке.
const tmpStringBufSize = 32
type tmpBuf [tmpStringBufSize]byte
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
var b []byte
// 如果字符串 s 的长度内部长度不超过 32, 那么就直接分配一个 32 直接的大小
if buf != nil && len(s) <= len(buf) {
*buf = tmpBuf{}
b = buf[:len(s)]
} else {
b = rawbyteslice(len(s))
}
copy(b, s)
return b
}
Если размер строки не превышает длины 32, по умолчанию выделяется buf длиной 32, что также является причиной, которую мы проанализировали выше.s.cap = 32
происхождение.
Пока мы еще не проанализировали проблемуfmt.Println(s1, "===", s2)
Причина, по которой эта закомментированная печать работает нормально? Так какова ситуация в итоге?
окончательный анализ
Наконец, мы включаем волшебный переключательfmt.Println(s1, "===", s2)
Провести оглашение последней тайны:
package main
import (
"fmt"
)
func main() {
s := []byte("")
println(s) // 添加用于打印信息
s1 := append(s, 'a')
s2 := append(s, 'b')
fmt.Println(s1, "===", s2)
fmt.Println(string(s1), string(s2))
}
$ go run q.go
[0/0]0x115b820 # 需要注意 s.len = 0 s.cap = 0
[97] === [98] # 取消了打印的注释
a b # 打印一切正常
$ go run -gcflags '-S -S' q.go
....
0x0032 00050 (q.go:8) MOVQ $0, (SP)
0x003a 00058 (q.go:8) MOVQ $0, 8(SP)
0x0043 00067 (q.go:8) MOVQ $0, 16(SP)
0x004c 00076 (q.go:8) PCDATA $0, $0
0x004c 00076 (q.go:8) CALL runtime.stringtoslicebyte(SB)
0x0051 00081 (q.go:8) MOVQ 32(SP), AX
0x0056 00086 (q.go:8) MOVQ AX, "".s.len+96(SP)
0x005b 00091 (q.go:8) MOVQ 40(SP), CX
0x0060 00096 (q.go:8) MOVQ CX, "".s.cap+104(SP)
0x0065 00101 (q.go:8) MOVQ 24(SP), DX
0x006a 00106 (q.go:8) MOVQ DX, "".s.ptr+136(SP)
....
В результате анализа обнаружено, что основной вызов по-прежнемуruntime.stringtoslicebyte()
, но поведение изменилосьs.len = s.cap = 0
, очевидно из-заfmt.Println(s1, "===", s2)
Появление линии приводит кs := []byte("")
Ситуация с выделением памяти изменилась.
Мы можем проанализировать это с помощью инструмента выделения памяти, предоставляемого go build:
$ go build -gcflags "-m -m" q.go
# command-line-arguments
./q.go:7:6: cannot inline main: non-leaf function
./q.go:14:13: s1 escapes to heap
./q.go:14:13: from ... argument (arg to ...) at ./q.go:14:13
./q.go:14:13: from *(... argument) (indirection) at ./q.go:14:13
./q.go:14:13: from ... argument (passed to call[argument content escapes]) at ./q.go:14:13
./q.go:8:13: ([]byte)("") escapes to heap
./q.go:8:13: from s (assigned) at ./q.go:8:4
./q.go:8:13: from s1 (assigned) at ./q.go:11:5
./q.go:8:13: from s1 (interface-converted) at ./q.go:14:13
./q.go:8:13: from ... argument (arg to ...) at ./q.go:14:13
./q.go:8:13: from *(... argument) (indirection) at ./q.go:14:13
./q.go:8:13: from ... argument (passed to call[argument content escapes]) at ./q.go:14:13
в приведенном выше выводеs1 escapes to heap
и([]byte)("") escapes to heap
показывает, что из-заfmt.Println(s1, "===", s2)
Введение кода привело к изменениям в модели назначения переменных. Проще говоря, он убегает из стека в кучу. Анализ побегов памяти будет подробно представлен в следующих главах. На данный момент общая идея уже есть, но как мы можем проверить это на уровне кода?runtime.stringtoslicebyte
место для начала. Файл, вызываемый поиском, находится вcmd/compile/internal/gc/walk.go
О строке [] следующий код называется байтовым анализом
case OSTRARRAYBYTE:
a := nodnil() // 分配到堆上的的默认行为
if n.Esc == EscNone {
// Create temporary buffer for slice on stack.
t := types.NewArray(types.Types[TUINT8], tmpstringbufsize)
a = nod(OADDR, temp(t), nil) // 分配在栈上,大小为32
}
n = mkcall("stringtoslicebyte", n.Type, init, a, conv(n.Left, types.Types[TSTRING]))
OSTRARRAYBYTE // Type(Left) (Type is []byte, Left is a string)
в приведенном выше кодеn.Esc == EscNone
Условный анализ показывает, что способ инициализации различен, когда происходит выход из памяти и когда не происходит выхода из памяти.Определение EscNone:
EscNone // Does not escape to heap, result, or parameters.
Благодаря приведенному выше анализу мы, наконец, нашли высшую тайну магии. Проанализированная выше версия языка go основана на 1.9.2.Механизм выделения памяти для разных языков go может быть разным.За подробностями обращайтесь к более подробному анализу моего коллеги.Ловушка преобразования строки в []byte в Go.md
Перейти к управлению памятью
Язык Go может автоматически управлять памятью, избегая проблем с самоуправлением памятью в языке C, но в то же время инкапсулируя управление памятью и повторяя детали кода, что также потенциально увеличивает сложность отладки и оптимизации системы. В то же время автоматическое управление памятью тоже очень сложная штука.Например, многоуровневые вызовы функций, вызовы замыканий, множественные назначения структур или пайпов, слайсов, вызовы MAP, CGO и т.д. механизм оптимизации управления дает сбой и вырождается в исходное состояние управления; действующая стратегия восстановления памяти (GC) также постоянно оптимизирует процесс. Начиная с первой версии Golang, GC подвергался наибольшей критике со стороны всех, но выпуск каждой версии в основном сопровождается GC улучшение. Некоторые из наиболее важных изменений перечислены ниже.
- v1.1 STW
- v1.3 Пометка STW, параллельная развертка
- v1.5 Метод трехцветной маркировки
- v1.8 hybrid write barrier
Основы разминки:How do I know whether a variable is allocated on the heap or the stack?
Анализ побега-Анализ побега
Для более глубокого и подробного понимания рекомендуется прочитать4 сообщения Уильяма Кеннеди
Go не обеспечивает точного управления распределением кучи и стека, как язык C, и поскольку он обеспечивает функцию автоматического управления памятью, он в значительной степени стирает границы между кучей и стеком. Например следующий код:
package main
func main() {
str := GetString()
_ = str
}
func GetString() *string {
var s string
s = "hello"
return &s
}
переменная в строке 10s = "hello"
несмотря на заявлениеGetString()
В функции, но вmain
Возвращенная переменная все еще может быть доступна в функции; такое поведение, которое, как локальная переменная, определенная в функции, может пробиться через свой собственный объем и доступна внешне, называется Escape, то есть переменная выделяется на кучу с помощью выхода, который можно выполнить по границам. Обмен данными.
Escape Analysis
Технология существует для этого сценария;Escape Analysis
технологии, компилятор будет анализировать код на этапе компиляции.Когда он обнаружит, что переменные в текущей области видимости не выходят за пределы области действия функции, они будут автоматически размещены в области видимости.stack
, иначе выделяется вheap
начальство. Освобождение памяти Go также нацелено на объекты в куче. идти
на языкеEscape Analysis
официалов пока не виделspec
документации, поэтому многие функции требуют пробного кода и анализа, чтобы сделать выводы и перейти кEscape Analysis
Существует множество реализацийнесовершенное место.
stack allocation is cheap and heap allocation is expensive.
Реализация escape-анализа языка Go
Читать больше рекомендаций по памятиAllocation efficiency in high-performance Go services
2.go
package main
import "fmt"
func main() {
x := 42
fmt.Println(x)
}
Флаг в Go Good Tool-gcflags '-m'
Резюме ситуации, которая может быть использована для анализа побегов памяти. Можно предоставить до 4 "-m". Чем больше m, тем более подробный анализ. В общем, мы можем использовать анализ двух m.
$ go build -gcflags '-m -l' 2.go
# command-line-arguments
./2.go:7:13: x escapes to heap
./2.go:7:13: main ... argument does not escape
# -l disable inline, 也可以调用的函数前添加注释
$ go build -gcflags '-m -m -l' 2.go
# command-line-arguments
./2.go:7:13: x escapes to heap
./2.go:7:13: from ... argument (arg to ...) at ./2.go:7:13
./2.go:7:13: from *(... argument) (indirection) at ./2.go:7:13
./2.go:7:13: from ... argument (passed to call[argument content escapes]) at ./2.go:7:13
./2.go:7:13: main ... argument does not escape
в примере вышеx escapes to heap
показывает, что переменнаяx
Переменная убегает в кучу. в-l
значит не включенinline
Вызовы шаблонов, иначе это усложнит анализ, также можно добавить комментарии над функцией//go:noinline
Встроенные вызовы функций запрещены. Что касается вызоваfmt.Println()
зачем вызыватьx escapes to heap
, вы можете обратиться кIssue #19720иIssue #8618, для вышеуказанногоfmt.Println()
Мы можем выполнить простой симуляционный тест со следующим кодом, и эффект в основном такой же:
package main
type pp struct {
arg interface{}
}
func MyPrintln(a ...interface{}) {
Fprintln(a...)
}
func Fprintln(a ...interface{}) (n int, err error) {
pp := new(pp)
pp.arg = a // 此处导致了内存的逃逸
return
}
func main() {
x := 42
MyPrintln(x)
}
Результаты анализа выхода из памяти следующие:
$ go build -gcflags '-m -m -l' 3.go
# command-line-arguments
./3.go:13:9: a escapes to heap
./3.go:13:9: from pp.arg (star-dot-equals) at ./3.go:13:9
./3.go:11:45: leaking param: a
./3.go:11:45: from a (interface-converted) at ./3.go:13:9
./3.go:11:45: from pp.arg (star-dot-equals) at ./3.go:13:9
./3.go:12:11: Fprintln new(pp) does not escape
./3.go:7:21: leaking param: a
./3.go:7:21: from a (passed to call[argument escapes]) at ./3.go:8:10
./3.go:19:11: ... argument escapes to heap
./3.go:19:11: from ... argument (passed to call[argument escapes]) at ./3.go:19:11
./3.go:19:11: x escapes to heap
./3.go:19:11: from ... argument (arg to ...) at ./3.go:19:11
./3.go:19:11: from ... argument (passed to call[argument escapes]) at ./3.go:19:11
Для общего анализа побега см.: Http://www.agardner.me/golang/grast/collection/gc/escape/analysys/2015/10/18/go-escape-analysis.html
Основные причины следующие: Переменныеx
Хотя он имеет тип int, он передается в функциюMyPrintln
функция преобразуется вinterface{}
типа потому чтоinterface{}
Тип содержит адрес к данным, поэтомуx
передается в функциюMyPrintln
Во время процесса осуществляется процесс перераспределения памяти, за счетpp.arg = a
Ссылка на назначение поля в структуре приводит к тому, что последующие переменные перемещаются в кучу. Если вышеpp.arg = a
Закомментируйте, памяти не будет.
Есть много ситуаций, которые приводят к выходу из памяти, и некоторые из них могут быть ошибками, которые чиновник не может добиться точного анализа ситуации выхода.Проще говоря, если область действия переменной не расширяется и ее поведение или размер можно определить на время компиляции, общая ситуация. Все нижеследующие выделяются в стек, в противном случае может произойти утечка памяти и выделение в кучу.
Кратко, есть следующие категории:
- Отправить указатель на указатель или значение, содержащее указатель на
channel
, так как его область действия и путь не могут быть определены на этапе компиляции, он обычно уходит в кучу для выделения. -
slices
Значение — это указатель на указатель или поле указателя. Один пример похож[] *string
тип. это всегда приводит кslice
побег. Несмотря на то, что базовый массив хранения среза может по-прежнему находиться в стеке, ссылки на данные перемещаются в кучу. -
slice
так какappend
работа превышает свои возможности, что приводит кslice
переназначить. В этом случае, поскольку во время компиляцииslice
будет размещен в стеке, когда известен начальный размер. еслиslice
Базовое хранилище должно масштабироваться только на основе данных времени выполнения, тогда оно будет выделено в куче. -
Вызовите метод типа интерфейса. Способ вызовов типов интерфейсов динамически отправляются - бетонная реализация фактически используемая может быть определена только во время выполнения. Рассмотрим тип интерфейса как
io.Reader
переменная р. правильноr.Read(b)
вызов приведет кr
значения и байтовые срезыb
последующие побеги и, следовательно, размещаются в куче. Ссылка http://npat-efault.github.io/programming/2016/10/10/escape-analysis-and-interfaces.html -
Хотя он может встретить сцену выделения в стек, но его размер нельзя определить во время компиляции, он также будет выделен в кучу
О указателях
Что касается использования указателей, то в большинстве случаев на нас влияет посылка: «Процесс передачи указателя не включает копирование базовых данных, поэтому он более эффективен», и в целом это верно.
Однако, поскольку доступ к указателю является косвенной адресацией, то есть после доступа к адресу, сохраненному указателем, необходимо выполнить другой доступ по сохраненному адресу, чтобы получить данные, на которые указывает указатель. Также необходимо оценивать ситуацию nil для предотвращения возникновения паники.Что еще более важно,большинство адресов,на которые указывают указатели,хранятся в куче.В случае восстановления памяти наличие указателей может заставить программу выполнять лучше скидка. Кроме того, из-за непрямого доступа к указателю оптимизация кеша также будет недействительной.Locality of reference, производительность копирования небольшого количества данных в кэш в основном эквивалентна производительности доступа по указателю.
Подводя итог, можно сказать, что использование указателей не обходится без затрат, и их нужно использовать разумно.
"сборщик мусора будет пропускать области памяти, которые, как он может доказать, не содержат указателей"
Проще говоря, если в структуре, выделенной в куче, будет меньше указателей, то механизм рециркуляции будет относительно простым, а эффективность рециркуляции должна быть повышена, необходимо понимать алгоритм go рециркуляции для соответствующих тестов. СДЕЛАТЬ
О конвертере интерфейсов
Для реализации интерфейса см.:Go Data Structures: Interfaces Go interfaces: static vs dynamic binding
На рисунке выше показана структура данных объекта Binary, преобразованного в интерфейс Stringer. Проверьте, совпадают ли типыs.tab->type
Вот и все.
на языке гоinterface
Интерфейс, статическая проверка неявного преобразования будет выполняться во время компиляции, но отображаемыйinterface
прибытьinterface
Преобразование может быть установлено при методах запроса выполнения, такими как обнаружение движения:
type Stringer interface {
String() string
}
if v, ok := any.(Stringer); ok {
return v.String()
}
оItab
Расчет конструкции, за счет (interface
,type
), компилятор или компоновщик go не может вычислить соответствующую взаимосвязь между ними во время компиляции, и даже если она может быть вычислена, большинство соответствующих взаимосвязей могут быть неприменимы на практике; поэтому компилятор go будет используется при компиляции дляinterface
иtype
Метод генерирует ассоциированную структуру описания, которая записывается отдельно.interface
иtype
Соответствующий набор методов языка go будет вtype
Фактическое динамическое преобразование вinterface
В ходе процессаinterafce
Метод, определенный вtype
Сравнивайте и ищите один за другим и улучшайтеItab
структура и воляItab
Структура кэшируется для повышения производительности.
Подводя итог, можно сказать, что вызовы методов интерфейсных типов в Go планируются динамически, поэтому их невозможно определить на этапе компиляции, а процесс преобразования всех типовых структур в интерфейсы будет связан с освобождением памяти.Для вызовов функций с высокими требованиями к производительности и высокой частотой доступа следует стараться избегать использования интерфейсных типов..
Следующий образец ссылки: http://npat-efault.github.io/programming/2016/10/10/escape-analysis-and-interfaces.html
package main
// go build -gcflags '-m -m -l' 5.go
type S struct {
s1 int
}
func (s *S) M1(i int) { s.s1 = i }
type I interface {
M1(int)
}
func main() {
var s1 S // this escapes
var s2 S // this does not
f1(&s1)
f2(&s2)
}
func f1(s I) { s.M1(42) }
func f2(s *S) { s.M1(42) }
Анализ побега подтверждает:
go build -gcflags '-m -m -l' 5.go
# command-line-arguments
./5.go:9:18: (*S).M1 s does not escape
./5.go:23:11: leaking param: s
./5.go:23:11: from s.M1(42) (receiver in indirect call) at ./5.go:23:21
./5.go:24:12: f2 s does not escape
./5.go:19:5: &s1 escapes to heap
./5.go:19:5: from &s1 (passed to call[argument escapes]) at ./5.go:19:4
./5.go:19:5: &s1 escapes to heap
./5.go:19:5: from &s1 (interface-converted) at ./5.go:19:5
./5.go:19:5: from &s1 (passed to call[argument escapes]) at ./5.go:19:4
./5.go:16:6: moved to heap: s1
./5.go:20:5: main &s2 does not escape
<autogenerated>:1:0: leaking param: .this
<autogenerated>:1:0: from .this.M1(.anon0) (receiver in indirect call) at <autogenerated>:1:0
Анализ тестов производительности:
package main_test
import "testing"
// go test -bench . --benchmem -gcflags "-N -l" 5_test.go
type S struct {
s1 int
}
func (s *S) M1(i int) {
s.s1 = i
}
type I interface {
M1(int)
}
func f1(s I) { s.M1(86) }
func f2(s *S) { s.M1(86) }
func BenchmarkTestInterface(b *testing.B) {
var s1 S
for i := 0; i < b.N; i++ {
f1(&s1)
}
}
func BenchmarkTestNoInterface(b *testing.B) {
var s2 S
for i := 0; i < b.N; i++ {
f2(&s2)
}
}
Запретить использованиеinline
Отчет о производительности вызова функции способа:
# 禁止使用 inline
$ go test -bench . --benchmem -gcflags "-N -l" 5_test.go
goos: darwin
goarch: amd64
BenchmarkTestInterface-8 300000000 4.50 ns/op 0 B/op 0 allocs/op
BenchmarkTestNoInterface-8 500000000 3.80 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 4.094s
включеноinline
Отчет о производительности вызова функции:
# 如果启用了 inline,性能差别非常明显
$ go test -bench . --benchmem 5_test.go
goos: darwin
goarch: amd64
BenchmarkTestInterface-8 500000000 3.45 ns/op 0 B/op 0 allocs/op
BenchmarkTestNoInterface-8 2000000000 0.29 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 2.685s
О фрагментах
Поскольку слайсы обычно используются в сценариях передачи функций, а слайсыappend
Это может быть связано с перераспределением памяти.Если размер слайса не может быть подтвержден во время компиляции или размер превышает предел стека, в большинстве случаев он будет выделен в кучу.
Проверка размера
package main
func main() {
s := make([]byte, 1, 1*1024)
_ = s
}
$ go build -gcflags "-m -m" slice_esc.go
# command-line-arguments
./slice_esc.go:3:6: can inline main as: func() { s := make([]byte, 1, 1 * 1024); _ = s }
./slice_esc.go:4:11: main make([]byte, 1, 1 * 1024) does not escape
еслиslice
Если размер превышает 64 КБ, он будет выделен в куче (перейдите к 1.9.2).
package main
func main() {
s := make([]byte, 1, 64*1024) // 64k
_ = s
}
$ go build -gcflags "-m -m" slice_esc.go
# command-line-arguments
./slice_esc.go:3:6: can inline main as: func() { s := make([]byte, 1, 64 * 1024); _ = s }
./slice_esc.go:4:11: make([]byte, 1, 64 * 1024) escapes to heap
./slice_esc.go:4:11: from make([]byte, 1, 64 * 1024) (too large for stack) at ./slice_esc.go:4:11
Проверка среза типа указателя
package main
func main() {
s := make([]*string, 1, 100)
str := "hello"
s = append(s, &str)
_ = s
}
$ go build -gcflags "-m -m -l" slice_esc.go
# command-line-arguments
./slice_esc.go:6:16: &str escapes to heap
./slice_esc.go:6:16: from append(s, &str) (appended to slice) at ./slice_esc.go:6:12
./slice_esc.go:5:9: moved to heap: str
./slice_esc.go:4:11: main make([]*string, 1, 100) does not escape
для экономии в[]*string
Строки выделяются непосредственно в куче.
package main
import "math/rand"
func main() {
randSize := rand.Int()
s := make([]*string, 0, randSize)
str := "hello"
s = append(s, &str)
_ = s
}
$ go build -gcflags "-m -m -l" slice_esc.go
# command-line-arguments
./slice_esc.go:7:11: make([]*string, 0, randSize) escapes to heap
./slice_esc.go:7:11: from make([]*string, 0, randSize) (too large for stack) at ./slice_esc.go:7:11
./slice_esc.go:9:16: &str escapes to heap
./slice_esc.go:9:16: from append(s, &str) (appended to slice) at ./slice_esc.go:9:12
./slice_esc.go:8:9: moved to heap: str
так какs := make([]*string, 0, randSize)
Размер не может быть определен при компиляции, поэтому он уходит в кучу.
Ссылаться на
- Анализ выхода из памяти Golang
- Углубленный анализ базовой реализации Slice in Go ***
- Понимание Go Memory Escapes с точки зрения C
- Сравнение строки golang и []byte
- Go Slices: usage and internals
- Where is append() implementation?
- SliceTricks ***
- Variadic func changes []byte(s) cap #24972
- spec: clarify that conversions to slices don't guarantee slice capacity? #24163
- Golang escape analysis ***
- Go Escape Analysis Flaws
- Escape Analysis for Java
- Language Mechanics On Escape Analysis китайский язык китайский 2
- Allocation efficiency in high-performance Go services ***
- Profiling Go Programs
- Github.com/mushroom sir ...
- the-go-programming-language-report
- https://golang.org/doc/faq
- Инвентаризация на конец года! Суперценные статьи Golang 2017 года
- Анатомия сборки мусора Голанга
- Углубитесь в сборку мусора Голанга