猿问

为什么运行时.newstack 分配比在堆上分配相同大小的变量便宜得多?

我试图理解Go的一些内部。但有一件事我不能绕开,那就是goroutines的堆栈是如何增加的。


我看到去使用来分配新的堆栈,然后将其复制到新创建的区域。虽然,它与堆分配有何不同?runtime.morestack


更准确地说:为什么函数的工作速度比函数快十倍?stack()heap()


func stack() {

    _ = stackit()

}


//go:noinline

func stackit() [8000]byte {

    return [8000]byte{}

}


func heap() {

    _ = heapit()

}


//go:noinline

func heapit() *[8000]byte {

    a := [8000]byte{}

    return &a

}

基准:


❯ go test -bench=. -benchmem

goos: darwin

goarch: amd64

cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz

BenchmarkStackIt-12     11208864           103.9 ns/op         0 B/op          0 allocs/op

BenchmarkHeapIt-12       1309708           919.5 ns/op      8192 B/op          1 allocs/op

PASS

ok      _/Users/asnelzin/dev/experiment/lstack  3.981s

如您所见,第一个函数中没有堆分配,但应放大 g.stack 以适合字节数组。


守着一只汪
浏览 142回答 1
1回答

阿晨1998

首先,正如@kostix所说,只有一个堆栈增长,因此基准测试不是这些测量的正确工具。我试图测量和函数的单次调用,但仍然得到了意想不到的结果:115ns vs 15us。stack()heap()所以我从顶部开始,找出哪个编译器确实在优化我的代码并完全删除堆栈分配。有 调用 ,但没有堆栈分配。stackit()我重写了示例并添加了一个打印件,以找到堆栈增长的时刻。package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "time")const size = 256func main() {&nbsp; &nbsp; print("main\n")&nbsp; &nbsp; start := time.Now()&nbsp; &nbsp; stack()&nbsp; &nbsp; fmt.Println(time.Since(start))}//go:noinlinefunc stack() {&nbsp; &nbsp; print("stack\n")&nbsp; &nbsp; x := [size]int64{}&nbsp; &nbsp; end(x)}//go:noinlinefunc heap() {&nbsp; &nbsp; print("heap\n")&nbsp; &nbsp; _ = heapit()}//go:noinlinefunc end(x [size]int64) {&nbsp; &nbsp; _ = x}//go:noinlinefunc heapit() *[size]int64 {&nbsp; &nbsp; return &[size]int64{}}使用编译的 go 版本运行此版本,得到以下输出:stackDebug = 1<...>mainruntime: newstack sp=0xc000070f10 stack=[0xc000070000, 0xc000071000]&nbsp; &nbsp; morebuf={pc:0x10a2dcc sp:0xc000070f20 lr:0x0}&nbsp; &nbsp; sched={pc:0x10a2f09 sp:0xc000070f18 lr:0x0 ctxt:0x0}stackalloc 16384stackcacherefill order=3&nbsp; allocated 0xc00010a000copystack gp=0xc000000180 [0xc000070000 0xc000070f18 0xc000071000] -> [0xc00010a000 0xc00010df18 0xc00010e000]/16384stackfree 0xc000070000 4096stack grow donestack50.096µs现在,堆栈显然正在增长到 16384 字节。这里重要的部分是将旧的堆栈复制到新的内存区域。堆版本不会更改堆栈的大小:mainheap6.096µs这个数字似乎不公平,所以我仔细检查了堆分配的内容,然后再次运行基准测试:逃生分析结果:> go build -gcflags '-m -l' -o sstack ./main.go./main.go:37:9: &[256]int64{} escapes to heap<...>基准测试结果:> go test -bench=. -benchmemgoos: darwingoarch: amd64cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHzBenchmarkStackIt-12&nbsp; &nbsp; &nbsp;24075922&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 43.80 ns/op&nbsp; &nbsp; &nbsp; &nbsp; 0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkHeapIt-12&nbsp; &nbsp; &nbsp; &nbsp;5042449&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;236.8 ns/op&nbsp; &nbsp; &nbsp; 2048 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1 allocs/opPASSok&nbsp; &nbsp; &nbsp; _/Users/asnelzin/dev/experiment/sstack&nbsp; 2.804s因此,是的,创建新堆栈并将旧堆栈的内容复制到新创建的堆栈会产生开销。但从长远来看,这种开销将与堆分配的开销相形见绌。
随时随地看视频慕课网APP

相关分类

Go
我要回答