英文:
What is considered "small" object in Go regarding stack allocation?
问题
代码中的a
和b
都是切片,但它们的长度不同。根据Go语言的规则,长度小于等于_MaxSmallSize
的切片会在栈上分配内存,而长度大于_MaxSmallSize
的切片会在堆上分配内存。
在你的代码中,a
的长度是8191,小于_MaxSmallSize
,所以它被分配在栈上。而b
的长度是8192,大于_MaxSmallSize
,所以它被分配在堆上。
_MaxSmallSize
的值是32 << 10,即32乘以1024,等于32768,也就是32KB。因此,长度小于等于32768的切片会在栈上分配内存,长度大于32768的切片会在堆上分配内存。
关于make
函数的使用,当分配的内存大小大于64KB时,切片会在堆上分配内存,否则会在栈上分配内存。
以上是关于为什么a
是小对象而b
是大对象的解释。
英文:
The code:
func MaxSmallSize() {
a := make([]int64, 8191)
b := make([]int64, 8192)
_ = a
_ = b
}
Then run go build -gcflags='-m' . 2>&1
to check memory allocation details. The result:
./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
My question is why a
is small object and b
is large object?
make
64KB will escape to heap and less will allocate in stack. Does the _MaxSmallSize = 32 << 10
is the reason?
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"
答案1
得分: 3
由于这在语言规范中没有提到,它是一个实现细节,因此可能会根据多种因素(Go版本、目标操作系统、架构等)而有所不同。
如果你想找出它的当前值或者开始查找的地方,请查看cmd/compile/internal/gc
包。
决定变量分配位置的逃逸分析在cmd/compile/internal/gc/esc.go
中。检查切片的make操作在未导出的函数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开始,它将在堆上分配(而不是栈上),就像你遇到的情况一样。
英文:
Since this is not mentioned in the language spec, it is an implementation detail, and as such, it may vary based on a number of things (Go version, target OS, architecture etc.).
If you want to find out its current value or a place to start digging, check out the cmd/compile/internal/gc
package.
The escape analysis which decides where to allocate the variable is in cmd/compile/internal/gc/esc.go
. Check of the make slice operation is in unexported function 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
}
// ...
}
The decision involving the size is in function isSmallMakeSlice()
, this is in file 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)
}
The size limit is this:
r.Int64() < (1<<16)/t.Elem().Width
r
is the length or capacity of the slice (if cap is provided), t.Elem().Width
is the byte size of the element type:
NumElem < 65536 / SizeElem
In your case:
NumElem < 65536 / 8 = 8192
So if the slice type is []uint64
, 8192 is the limit from which it is allocated on the heap (instead of the stack), just as you experienced.
答案2
得分: 2
@icza的回答非常有见地,我只想补充一点,链接有点过时了,5年后你可以在cmd/compile/internal/escape/utils.go和cmd/compile/internal/ir/cfg.go中找到代码:
// HeapAllocReason返回给定节点必须分配在堆上的原因,如果不需要分配则返回空字符串。
func HeapAllocReason(n ir.Node) string {
// ... 省略部分代码
if n.Op() == ir.OMAKESLICE {
n := n.(*ir.MakeExpr)
r := n.Cap
if r == nil {
r = n.Len
}
if !ir.IsSmallIntConst(r) {
return "非常数大小"
}
if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Size() {
return "太大,无法放在栈上"
}
}
return ""
}
而ir.MaxImplicitStackVarSize
是:
package ir
var (
// 在栈上分配的隐式变量的最大大小。
// p := new(T) 在栈上分配T
// p := &T{} 在栈上分配T
// s := make([]T, n) 在栈上分配[n]T
// s := []byte("...") 在栈上分配[n]byte
// 注意:标志smallframes可以更新此值。
MaxImplicitStackVarSize = int64(64 * 1024)
)
英文:
@icza's answer is really insightful, I'd just like to add that the link is a bit outdated 5 years later, you can find the code in cmd/compile/internal/escape/utils.go and in turn cmd/compile/internal/ir/cfg.go now:
// 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 ""
}
and ir.MaxImplicitStackVarSize
is:
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)
)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论