为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
守着一只汪 2022-08-15 19:18:49
我试图理解Go的一些内部。但有一件事我不能绕开,那就是goroutines的堆栈是如何增加的。我看到去使用来分配新的堆栈,然后将其复制到新创建的区域。虽然,它与堆分配有何不同?runtime.morestack更准确地说:为什么函数的工作速度比函数快十倍?stack()heap()func stack() {    _ = stackit()}//go:noinlinefunc stackit() [8000]byte {    return [8000]byte{}}func heap() {    _ = heapit()}//go:noinlinefunc heapit() *[8000]byte {    a := [8000]byte{}    return &a}基准:❯ go test -bench=. -benchmemgoos: darwingoarch: amd64cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHzBenchmarkStackIt-12     11208864           103.9 ns/op         0 B/op          0 allocs/opBenchmarkHeapIt-12       1309708           919.5 ns/op      8192 B/op          1 allocs/opPASSok      _/Users/asnelzin/dev/experiment/lstack  3.981s如您所见,第一个函数中没有堆分配,但应放大 g.stack 以适合字节数组。
查看完整描述

1 回答

?
阿晨1998

TA贡献2037条经验 获得超6个赞

首先,正如@kostix所说,只有一个堆栈增长,因此基准测试不是这些测量的正确工具。我试图测量和函数的单次调用,但仍然得到了意想不到的结果:115ns vs 15us。stack()heap()


所以我从顶部开始,找出哪个编译器确实在优化我的代码并完全删除堆栈分配。有 调用 ,但没有堆栈分配。stackit()


我重写了示例并添加了一个打印件,以找到堆栈增长的时刻。


package main


import (

    "fmt"

    "time"

)


const size = 256


func main() {

    print("main\n")

    start := time.Now()

    stack()

    fmt.Println(time.Since(start))

}


//go:noinline

func stack() {

    print("stack\n")

    x := [size]int64{}

    end(x)

}


//go:noinline

func heap() {

    print("heap\n")

    _ = heapit()

}


//go:noinline

func end(x [size]int64) {

    _ = x

}


//go:noinline

func heapit() *[size]int64 {

    return &[size]int64{}

}

使用编译的 go 版本运行此版本,得到以下输出:stackDebug = 1


<...>

main

runtime: newstack sp=0xc000070f10 stack=[0xc000070000, 0xc000071000]

    morebuf={pc:0x10a2dcc sp:0xc000070f20 lr:0x0}

    sched={pc:0x10a2f09 sp:0xc000070f18 lr:0x0 ctxt:0x0}

stackalloc 16384

stackcacherefill order=3

  allocated 0xc00010a000

copystack gp=0xc000000180 [0xc000070000 0xc000070f18 0xc000071000] -> [0xc00010a000 0xc00010df18 0xc00010e000]/16384

stackfree 0xc000070000 4096

stack grow done

stack

50.096µs

现在,堆栈显然正在增长到 16384 字节。这里重要的部分是将旧的堆栈复制到新的内存区域。


堆版本不会更改堆栈的大小:


main

heap

6.096µs

这个数字似乎不公平,所以我仔细检查了堆分配的内容,然后再次运行基准测试:


逃生分析结果:


> go build -gcflags '-m -l' -o sstack ./main.go

./main.go:37:9: &[256]int64{} escapes to heap

<...>

基准测试结果:


> go test -bench=. -benchmem

goos: darwin

goarch: amd64

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

BenchmarkStackIt-12     24075922            43.80 ns/op        0 B/op          0 allocs/op

BenchmarkHeapIt-12       5042449           236.8 ns/op      2048 B/op          1 allocs/op

PASS

ok      _/Users/asnelzin/dev/experiment/sstack  2.804s

因此,是的,创建新堆栈并将旧堆栈的内容复制到新创建的堆栈会产生开销。但从长远来看,这种开销将与堆分配的开销相形见绌。


查看完整回答
反对 回复 2022-08-15
  • 1 回答
  • 0 关注
  • 141 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号