Что считается "маленьким" объектом в Go относительно распределения стека?
Код:
func MaxSmallSize() {
a := make([]int64, 8191)
b := make([]int64, 8192)
_ = a
_ = b
}
Тогда беги go build -gcflags='-m' . 2>&1
проверить детали выделения памяти. Результат:
./mem.go:10: can inline MaxSmallSize
./mem.go:12: make([]int64, 8192) escapes to heap
./mem.go:11: MaxSmallSize make([]int64, 8191) does not escape
Мой вопрос почему a
маленький объект и b
это большой объект?
make
64 КБ уйдет в кучу и меньше будет выделяться в стеке. Ли _MaxSmallSize = 32 << 10
это причина?
go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/vagrant/gopath"
GORACE=""
GOROOT="/home/vagrant/go"
GOTOOLDIR="/home/vagrant/go/pkg/tool/linux_amd64"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build201775001=/tmp/go-build"
CXX="g++"
CGO_ENABLED="1"
2 ответа
Поскольку это не упоминается в спецификации языка, это подробности реализации, и, как таковое, оно может варьироваться в зависимости от ряда факторов (версия Go, целевая ОС, архитектура и т. Д.).
Если вы хотите узнать его текущее значение или место, чтобы начать копать, проверьте cmd/compile/internal/gc
пакет.
Escape-анализ, который решает, где разместить переменную, находится в cmd/compile/internal/gc/esc.go
, Проверка операции make slice в неэкспортированной функции esc()
:
func esc(e *EscState, n *Node, up *Node) {
// ...
// Big stuff escapes unconditionally
// "Big" conditions that were scattered around in walk have been gathered here
if n.Esc != EscHeap && n.Type != nil &&
(n.Type.Width > MaxStackVarSize ||
(n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= 1<<16 ||
n.Op == OMAKESLICE && !isSmallMakeSlice(n)) {
if Debug['m'] > 2 {
Warnl(n.Lineno, "%v is too large for stack", n)
}
n.Esc = EscHeap
addrescapes(n)
escassignSinkNilWhy(e, n, n, "too large for stack") // TODO category: tooLarge
}
// ...
}
Решение с участием размера находится в функции isSmallMakeSlice()
это в файле cmd/compile/internal/gc/walk.go
:
func isSmallMakeSlice(n *Node) bool {
if n.Op != OMAKESLICE {
return false
}
l := n.Left
r := n.Right
if r == nil {
r = l
}
t := n.Type
return Smallintconst(l) && Smallintconst(r) && (t.Elem().Width == 0 || r.Int64() < (1<<16)/t.Elem().Width)
}
Ограничение размера:
r.Int64() < (1<<16)/t.Elem().Width
r
длина или емкость среза (если предусмотрена крышка), t.Elem().Width
размер байта типа элемента:
NumElem < 65536 / SizeElem
В твоем случае:
NumElem < 65536 / 8 = 8192
Так что, если тип среза []uint64
, 8192 - это предел, из которого он выделяется в куче (а не в стеке), как вы уже видели.
Ответ @icza действительно проницателен, я просто хотел бы добавить, что ссылка немного устарела спустя 5 лет, вы можете найти код в cmd/compile/internal/escape/utils.go и, в свою очередь, в cmd/compile/internal /ir/cfg.go сейчас:
// HeapAllocReason returns the reason the given Node must be heap
// allocated, or the empty string if it doesn't.
func HeapAllocReason(n ir.Node) string {
// ... omitted for brevity
if n.Op() == ir.OMAKESLICE {
n := n.(*ir.MakeExpr)
r := n.Cap
if r == nil {
r = n.Len
}
if !ir.IsSmallIntConst(r) {
return "non-constant size"
}
if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Size() {
return "too large for stack"
}
}
return ""
}
иir.MaxImplicitStackVarSize
является:
package ir
var (
// maximum size of implicit variables that we will allocate on the stack.
// p := new(T) allocating T on the stack
// p := &T{} allocating T on the stack
// s := make([]T, n) allocating [n]T on the stack
// s := []byte("...") allocating [n]byte on the stack
// Note: the flag smallframes can update this value.
MaxImplicitStackVarSize = int64(64 * 1024)
)