【问题标题】:What is considered "small" object in Go regarding stack allocation?关于堆栈分配,什么被认为是 Go 中的“小”对象?
【发布时间】:2017-07-03 17:58:33
【问题描述】:

代码:

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 64KB 将逃逸到堆中,而更少的将分配到堆栈中。请问_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"

【问题讨论】:

  • 这取决于实现,不同的编译器可能会做不同的事情,不同的架构可能会做不同的事情,不同的版本可能会以不同的方式处理它。那么你的问题到底是什么?

标签: go slice allocation escape-analysis


【解决方案1】:

由于语言规范中没有提到这一点,它是一个实现细节,因此,它可能会因许多因素(Go 版本、目标操作系统、架构等)而有所不同。

如果您想了解它的当前价值或开始挖掘的地方,请查看cmd/compile/internal/gc 包。

决定在哪里分配变量的escape analysiscmd/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 是它在堆(而不是堆栈)上分配的限制,正如您所经历的那样。

【讨论】:

    猜你喜欢
    • 2011-03-27
    • 2020-11-11
    • 1970-01-01
    • 2010-11-24
    • 2020-12-19
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    相关资源
    最近更新 更多