2 回答
TA贡献1815条经验 获得超6个赞
这些问题有点难以回答,因为 goroutine 堆栈是从堆中分配的。Go 没有 C 中存在的堆栈和堆之间的明确分离。
总分配字段是否包括堆、堆栈或两者?
MemStats 结构的 TotalAlloc 字段包括 Go 运行时为 Go 堆从 OS 请求的所有内存。它不包括为 goroutine 堆栈分配的内存。一开始我以为是这样,但我错了。对困惑感到抱歉。我希望这个答案更准确。
(准确地说,我应该提到,在使用 cgo 的程序中,每个线程(不是 goroutine——goroutine 通常比线程多)都有一个由操作系统分配的堆栈;该堆栈不是由 Go 运行时分配的,而是不计入 TotalAlloc。它只被 cgo 调用使用。)
哪些堆字段不包括数字堆栈? 哪些堆字段包括堆栈的数字?
这些字段包括 goroutine 堆栈的编号:HeapIdle、HeapReleased。
这些字段不包括 goroutine 堆栈的数字:HeapAlloc、HeapInUse、HeapObjects。
HeapSys 字段不包括当前活动的 goroutine 堆栈使用的内存,但包括曾经使用但随后被释放的 goroutine 堆栈的内存。
哪些堆栈字段不包含堆的数字?
我不知道如何以有意义的方式回答这个问题。堆栈字段专门报告有关 goroutine 堆栈的信息。
TA贡献1712条经验 获得超3个赞
通过运行(变体)测试程序并查看 Go 源代码,我看到:
Alloc 和 TotalAlloc 似乎只涵盖非堆栈分配。分配大局部变量并没有将它们的大小增加到 TotalAlloc,即使它导致堆栈增长。
如果内存当前是为 goroutine 的堆栈保留的,它会计入 StackX 变量而不是 HeapX 变量。这是您在源代码中找到的减法。它还意味着为堆栈分配空间的任何事情都可以减少 HeapSys 和 HeapIdle 但不影响 HeapInuse。
因为你问:堆栈字段从不包括堆分配——堆栈来自堆,反之亦然。
ssp := new(SomeStruct)
如果逃逸分析可以确定它们不会超过函数调用,那么您认为在堆 ( )上的变量实际上可能是堆栈分配的。这几乎总是对您有用,因为可以在函数退出时释放这些变量,而不会为 GC 生成垃圾。不要太担心这个。:)一旦 goroutine 退出,它的堆栈空间可以返回到堆中。(不过,如果它的堆栈很小,它可能会被缓存以作为未来 goroutine 的堆栈重用。)然后它不会显示为堆栈空间,并可能再次显示为可用的堆空间。我在经验和 Go 源代码中都看到了这一点(proc.c 的 gfput 调用 runtime·stackfree)。这意味着退出 goroutines 或在堆栈增长后返回的旧堆栈似乎会增长 HeapSys 和 HeapIdle,但这实际上只是使用之间的空间转移。
似乎没有 TotalAlloc 风格的运行计数器覆盖为堆栈分配的所有页面。如果一个 goroutine 的堆栈被释放和重用,它只会被计数一次。
绝对没有 TotalAlloc 风格的运行计数器涵盖所有堆栈分配的变量。这将涉及跟踪每个函数调用的开销。
堆栈相关的问题相对较少,因为堆栈分配的变量在函数返回时被释放,而大堆栈本身在 goroutine 退出时被释放。它们可能会发生,比如如果 goroutine 泄漏(即使在创建新的 goroutine 时也不会退出),或者如果您var bigLocal [1e7]uint64
在不退出的 goroutine 中进行了巨大的堆栈分配(或荒谬的深度递归)。但是更常见的是堆出现问题,因为堆上的东西直到 GC 才会被释放(像标准这样的工具可以Pool
帮助您回收堆分配的项目以延迟对 GC 的需求)。
因此,实际上,我主要会关注 Alloc 和 TotalAlloc 是否存在堆过度使用的情况,如果不知何故堆栈空间成为问题,请查看堆栈编号(并且可能会检查是否有许多意外运行的 goroutines)。
这些观察是特定于实现的(我在看 go 1.4,而不是提示),而且我不是 Go 源代码的专家,所以就看它们是什么。那个测试程序,供参考:
package main
import (
"fmt"
"reflect"
"runtime"
"sync"
)
var g []byte
func usesHeap() {
g = make([]byte, 1000)
}
func usesTempStack() {
var l [1000]byte
_ = l
}
func createsGoroutineAndWaits() {
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
usesTempStack()
wg.Done()
}()
wg.Wait()
}
func createsGoroutine() {
go usesTempStack()
}
func recurse(depth int, max int) {
var l [1024]byte
_ = l
if depth < max {
recurse(depth+1, max)
}
}
func growsTheStack() {
recurse(0, 1000)
}
func checkUsageOf(lbl string, f func(), before, after *runtime.MemStats) {
_ = new(sync.WaitGroup)
runtime.ReadMemStats(before)
// using own goroutine so everyone starts w/the same stack size
wg := new(sync.WaitGroup)
wg.Add(1)
// request GC in hopes of a fair start
runtime.GC()
go func() {
runtime.ReadMemStats(before)
for i := 0; i < 1000; i++ {
f()
}
runtime.Gosched()
runtime.ReadMemStats(after)
wg.Done()
}()
wg.Wait()
fmt.Println("Results for", lbl, "\n")
beforeVal, afterVal := reflect.ValueOf(*before), reflect.ValueOf(*after)
memStatsType := beforeVal.Type()
fieldCount := memStatsType.NumField()
for i := 0; i < fieldCount; i++ {
field := memStatsType.Field(i)
if field.Type.Kind() != reflect.Uint64 {
continue
}
beforeStat, afterStat := int64(beforeVal.Field(i).Uint()), int64(afterVal.Field(i).Uint())
if beforeStat == afterStat {
continue
}
fmt.Println(field.Name, "differs by", afterStat-beforeStat)
}
fmt.Println("\n")
}
func main() {
before, after := new(runtime.MemStats), new(runtime.MemStats)
checkUsageOf("growsTheStack", growsTheStack, before, after)
checkUsageOf("createsGoroutineAndWaits", createsGoroutine, before, after)
checkUsageOf("usesHeap", usesHeap, before, after)
checkUsageOf("usesTempStack", usesTempStack, before, after)
checkUsageOf("createsGoroutine", createsGoroutine, before, after)
}
- 2 回答
- 0 关注
- 221 浏览
添加回答
举报