在Go语言中,关于堆栈分配,什么被认为是“小”对象?

huangapple go评论75阅读模式
英文:

What is considered "small" object in Go regarding stack allocation?

问题

代码中的ab都是切片,但它们的长度不同。根据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=&#39;-m&#39; . 2&gt;&amp;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 &lt;&lt; 10 is the reason?

go env

GOARCH=&quot;amd64&quot;
GOBIN=&quot;&quot;
GOEXE=&quot;&quot;
GOHOSTARCH=&quot;amd64&quot;
GOHOSTOS=&quot;linux&quot;
GOOS=&quot;linux&quot;
GOPATH=&quot;/vagrant/gopath&quot;
GORACE=&quot;&quot;
GOROOT=&quot;/home/vagrant/go&quot;
GOTOOLDIR=&quot;/home/vagrant/go/pkg/tool/linux_amd64&quot;
CC=&quot;gcc&quot;
GOGCCFLAGS=&quot;-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build201775001=/tmp/go-build&quot;
CXX=&quot;g++&quot;
CGO_ENABLED=&quot;1&quot;

答案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
	// &quot;Big&quot; conditions that were scattered around in walk have been gathered here
	if n.Esc != EscHeap &amp;&amp; n.Type != nil &amp;&amp;
		(n.Type.Width &gt; MaxStackVarSize ||
			(n.Op == ONEW || n.Op == OPTRLIT) &amp;&amp; n.Type.Elem().Width &gt;= 1&lt;&lt;16 ||
			n.Op == OMAKESLICE &amp;&amp; !isSmallMakeSlice(n)) {
		if Debug[&#39;m&#39;] &gt; 2 {
			Warnl(n.Lineno, &quot;%v is too large for stack&quot;, n)
		}
		n.Esc = EscHeap
		addrescapes(n)
		escassignSinkNilWhy(e, n, n, &quot;too large for stack&quot;) // 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) &amp;&amp; Smallintconst(r) &amp;&amp; (t.Elem().Width == 0 || r.Int64() &lt; (1&lt;&lt;16)/t.Elem().Width)
}

The size limit is this:

r.Int64() &lt; (1&lt;&lt;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 &lt; 65536 / SizeElem

In your case:

NumElem &lt; 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.gocmd/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&#39;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 &quot;non-constant size&quot;
		}
		if t := n.Type(); t.Elem().Size() != 0 &amp;&amp; ir.Int64Val(r) &gt; ir.MaxImplicitStackVarSize/t.Elem().Size() {
			return &quot;too large for stack&quot;
		}
	}

	return &quot;&quot;
}

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 := &amp;T{}            allocating T on the stack
	//   s := make([]T, n)    allocating [n]T on the stack
	//   s := []byte(&quot;...&quot;)   allocating [n]byte on the stack
	// Note: the flag smallframes can update this value.
	MaxImplicitStackVarSize = int64(64 * 1024)
)

huangapple
  • 本文由 发表于 2017年2月15日 15:34:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/42243197.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定