Go运行时有很多与堆和堆栈相关的不同变量,一些堆栈号是堆号的一部分,导致混乱(对我来说)。例如,in this link。它说
// Stack numbers are part of the heap numbers, separate those out for user consumption
stats.StackSys = stats.StackInuse
stats.HeapInuse -= stats.StackInuse
stats.HeapSys -= stats.StackInuse
在runtime docs(下面的摘录)中,它提供了7个不同的堆相关字段(即memstat结构的字段),但没有明确说明哪些包含堆栈,类似地,哪些堆栈字段包含在堆中,以及它与总分配的关系。
这是一个问题,因为我想比较堆与堆栈,但我不想选择包含堆栈的堆变量(显然)。
问题 1)。总分配字段是包含堆,堆栈还是两者? 2)哪些堆字段不包含数字堆栈? 3)哪些堆字段包含堆栈的数字? 4)哪些堆栈字段不包含堆的数字?
Alloc uint64 // bytes allocated and still in use
TotalAlloc uint64 // bytes allocated (even if freed)
Sys uint64 // bytes obtained from system (sum of XxxSys below)
Lookups uint64 // number of pointer lookups
Mallocs uint64 // number of mallocs
Frees uint64 // number of frees
// Main allocation heap statistics.
HeapAlloc uint64 // bytes allocated and still in use
HeapSys uint64 // bytes obtained from system
HeapIdle uint64 // bytes in idle spans
HeapInuse uint64 // bytes in non-idle span
HeapReleased uint64 // bytes released to the OS
HeapObjects uint64 // total number of allocated objects
// Low-level fixed-size structure allocator statistics.
// Inuse is bytes used now.
// Sys is bytes obtained from system.
StackInuse uint64 // bytes used by stack allocator
StackSys uint64
答案 0 :(得分:6)
这些问题有点难以回答,因为goroutine堆栈是从堆中分配的。 Go没有C中存在的堆栈和堆之间的明确分离。
总分配字段是否包含堆,堆栈或两者?
MemStats结构的TotalAlloc字段包括Go运行时从操作系统为Go堆请求的所有内存。它不包括为goroutine堆栈分配的内存。最初我认为它确实如此,但我错了。对困惑感到抱歉。我希望这个答案更准确。
(确切地说,我应该提到在一个使用cgo的程序中,每个线程(不是goroutine - 通常比线程更多的goroutine)将有一个由OS分配的堆栈;该堆栈不是由Go分配的运行时并且不计入TotalAlloc。它仅用于cgo调用。)
哪些堆字段不包含数字堆栈? 哪些堆字段包含堆栈数?
这些字段包括goroutine堆栈的数字:HeapIdle,HeapReleased。
这些字段不包含goroutine堆栈的数字:HeapAlloc,HeapInUse,HeapObjects。
HeapSys字段不包括当前活动的goroutine堆栈使用的内存,但包括曾经使用但随后被释放的goroutine堆栈的内存。
哪些堆栈字段不包含堆数?
我不知道如何以有道理的方式回答这个问题。堆栈字段专门报告有关goroutine堆栈的信息。
答案 1 :(得分:4)
从运行(变体)a test program并偷看Go源代码,我看到了:
Alloc和TotalAlloc似乎只涵盖非堆栈分配。分配大型本地人并没有将他们的大小添加到TotalAlloc,即使它导致堆栈增长。
如果当前为goroutine的堆栈保留了内存,则它在StackX变量而不是HeapX变量中计数。这是您在源中找到的减法。它还意味着为堆栈分配空间的任何东西都可以减少HeapSys和HeapIdle,但只留下HeapInuse。
ssp := new(SomeStruct)
)实际上可能是堆栈分配的。这几乎总是对你有用,因为这些变量可以在函数出口处释放而不会为GC生成垃圾。不要太担心这个。 :)goroutine退出后,其堆栈空间可以返回堆中。 (但是,如果它的堆栈很小,它可能会被缓存以作为未来的goroutine堆栈重用。)然后它不会显示为堆栈空间并且可能再次显示为可用堆空间。我在经验上和Go源中都看到了这一点(proc.c的gfput调用运行时·stackfree)。这意味着在堆栈增长后返回的退出goroutine或旧堆栈似乎会增加HeapSys和HeapIdle,但它实际上只是在使用之间进行空间转换。
似乎没有TotalAlloc样式的运行计数器覆盖为堆栈分配的所有页面。如果goroutine的堆栈被释放并重用,它只会被计数一次。
肯定没有TotalAlloc风格的运行计数器覆盖所有堆栈分配的变量。这将涉及跟踪每个函数调用的开销。
与堆栈相关的问题相对罕见,因为在函数返回时释放堆栈分配的变量,并且在goroutine exit上释放大堆栈本身。它们可以发生,比如goroutines正在泄漏(即使你创建新的也没有退出),或者如果你在goroutines中进行大量的堆栈分配(var bigLocal [1e7]uint64
或荒谬的深度递归)不要退出但是在堆上遇到麻烦更常见,因为堆上的东西在GC之前没有被释放(标准Pool
之类的工具可以帮助你回收堆分配的项目延迟需要GC)。
所以,实际上,我会主要关注Alloc和TotalAlloc的堆过度使用,如果堆栈空间不知何故成为问题,请查看堆栈数(可能是check for unexpectedly many running goroutines)。
这些观察结果是特定于实现的(我正在考虑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)
}