memstats struct 中的哪些字段只引用堆,只引用堆栈
which fields in memstats struct refer only to heap, only to stack
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个不同的heap相关字段(即memstat结构体的字段),没有明确说明哪些包含stack,同理,包含哪些stack字段在堆中,以及它与总分配的关系。
这是一个问题,因为我想将堆与堆栈进行比较,但我不想选择包含堆栈的堆变量(显然)。
问题
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
这些问题有点难回答,因为协程栈是从堆中分配的。 Go 没有 C 中存在的堆栈和堆之间的明确分离。
总分配字段是否包括堆、堆栈或两者?
MemStats 结构的 TotalAlloc 字段包括 Go 运行时从 OS 为 Go 堆请求的所有内存。它不包括为 goroutine 堆栈分配的内存。起初我认为是的,但我错了。对困惑感到抱歉。希望这个回答更准确。
(准确地说,我应该提到,在使用 cgo 的程序中,每个线程(不是 goroutine——通常 goroutine 比线程多)将有一个由 OS 分配的堆栈;那个堆栈不被Go运行时分配,不计入TotalAlloc,仅供cgo调用使用。)
哪些堆字段不包括数字堆栈?
哪些堆字段包含堆栈编号?
这些字段包括 goroutine 堆栈的数字:HeapIdle、HeapReleased。
这些字段不包括 goroutine 堆栈的数字:HeapAlloc、HeapInUse、HeapObjects。
HeapSys 字段不包括当前活动的 goroutine 堆栈使用的内存,但包括曾经使用但随后被释放的 goroutine 堆栈的内存。
哪些堆栈字段不包括堆的数字?
我不知道如何以有意义的方式回答这个问题。堆栈字段报告专门关于 goroutine 堆栈的信息。
来自 运行(变体)a test program 并查看 Go 源代码,我看到:
Alloc 和 TotalAlloc 似乎只涵盖非堆栈分配。分配 big locals 不会将它们的大小添加到 TotalAlloc,即使它导致堆栈增长。
如果内存当前是为 goroutine 的堆栈保留的,那么它将计入 StackX 变量而不是 HeapX 变量。这是您在源代码中找到的减法。它还意味着任何为堆栈分配 space 的东西都可以减少 HeapSys 和 HeapIdle,但不影响 HeapInuse。
- 因为您问过:堆栈字段从不包括堆分配——堆栈来自堆,反之则不然。
- 您认为在堆上的变量 (
ssp := new(SomeStruct)
) 实际上可能是堆栈分配的,如果逃逸分析可以确定它们不会超过函数调用。这几乎总是对您有用,因为这些变量可以在函数退出时被释放,而不会为 GC 产生垃圾。不要太担心这个。 :)
一旦 goroutine 退出,它的堆栈 space 可以 被 return 编辑到堆中。 (不过,如果它的堆栈很小,它很可能被缓存起来以作为未来 goroutine 的堆栈重用。)然后它不会显示为堆栈 space 并且可能再次显示为可用堆 space 。我在经验和 Go 源代码中都看到了这一点(proc.c 的 gfput 调用运行时·stackfree)。这意味着退出 goroutines 或旧堆栈在堆栈增长后被 returned 看起来会增长 HeapSys 和 HeapIdle,但它实际上只是 space 在使用之间转换。
似乎没有 TotalAlloc-style 运行 计数器涵盖曾经为堆栈分配的所有页面。如果一个 goroutine 的堆栈被释放并重新使用,它只会被计算一次。
绝对没有 TotalAlloc 风格的 运行 计数器涵盖所有堆栈分配的变量。这将涉及跟踪每个函数调用的开销。
堆栈相关的问题相对较少,因为堆栈分配的变量在函数 return 上被释放,而大堆栈本身在 goroutine 退出时被释放。它们 可以 发生,比如 goroutines 正在泄漏(即使你创建新的 goroutines 也永远不会退出),或者如果你在不退出的协程。但是堆上的问题更常见,因为堆上的东西直到 GC 才被释放(像标准 Pool
这样的工具可以帮助你 回收 堆分配延迟 GC 需求的项目)。
所以,实际上,我主要关注 Alloc 和 TotalAlloc 是否过度使用堆,如果堆栈 space 以某种方式成为问题,请查看堆栈编号(也许 check for unexpectedly many running goroutines ).
这些观察结果是特定于实现的(我正在看 go 1.4,不是 tip),而且我不是 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)
}
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个不同的heap相关字段(即memstat结构体的字段),没有明确说明哪些包含stack,同理,包含哪些stack字段在堆中,以及它与总分配的关系。
这是一个问题,因为我想将堆与堆栈进行比较,但我不想选择包含堆栈的堆变量(显然)。
问题 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
这些问题有点难回答,因为协程栈是从堆中分配的。 Go 没有 C 中存在的堆栈和堆之间的明确分离。
总分配字段是否包括堆、堆栈或两者?
MemStats 结构的 TotalAlloc 字段包括 Go 运行时从 OS 为 Go 堆请求的所有内存。它不包括为 goroutine 堆栈分配的内存。起初我认为是的,但我错了。对困惑感到抱歉。希望这个回答更准确。
(准确地说,我应该提到,在使用 cgo 的程序中,每个线程(不是 goroutine——通常 goroutine 比线程多)将有一个由 OS 分配的堆栈;那个堆栈不被Go运行时分配,不计入TotalAlloc,仅供cgo调用使用。)
哪些堆字段不包括数字堆栈? 哪些堆字段包含堆栈编号?
这些字段包括 goroutine 堆栈的数字:HeapIdle、HeapReleased。
这些字段不包括 goroutine 堆栈的数字:HeapAlloc、HeapInUse、HeapObjects。
HeapSys 字段不包括当前活动的 goroutine 堆栈使用的内存,但包括曾经使用但随后被释放的 goroutine 堆栈的内存。
哪些堆栈字段不包括堆的数字?
我不知道如何以有意义的方式回答这个问题。堆栈字段报告专门关于 goroutine 堆栈的信息。
来自 运行(变体)a test program 并查看 Go 源代码,我看到:
Alloc 和 TotalAlloc 似乎只涵盖非堆栈分配。分配 big locals 不会将它们的大小添加到 TotalAlloc,即使它导致堆栈增长。
如果内存当前是为 goroutine 的堆栈保留的,那么它将计入 StackX 变量而不是 HeapX 变量。这是您在源代码中找到的减法。它还意味着任何为堆栈分配 space 的东西都可以减少 HeapSys 和 HeapIdle,但不影响 HeapInuse。
- 因为您问过:堆栈字段从不包括堆分配——堆栈来自堆,反之则不然。
- 您认为在堆上的变量 (
ssp := new(SomeStruct)
) 实际上可能是堆栈分配的,如果逃逸分析可以确定它们不会超过函数调用。这几乎总是对您有用,因为这些变量可以在函数退出时被释放,而不会为 GC 产生垃圾。不要太担心这个。 :)
一旦 goroutine 退出,它的堆栈 space 可以 被 return 编辑到堆中。 (不过,如果它的堆栈很小,它很可能被缓存起来以作为未来 goroutine 的堆栈重用。)然后它不会显示为堆栈 space 并且可能再次显示为可用堆 space 。我在经验和 Go 源代码中都看到了这一点(proc.c 的 gfput 调用运行时·stackfree)。这意味着退出 goroutines 或旧堆栈在堆栈增长后被 returned 看起来会增长 HeapSys 和 HeapIdle,但它实际上只是 space 在使用之间转换。
似乎没有 TotalAlloc-style 运行 计数器涵盖曾经为堆栈分配的所有页面。如果一个 goroutine 的堆栈被释放并重新使用,它只会被计算一次。
绝对没有 TotalAlloc 风格的 运行 计数器涵盖所有堆栈分配的变量。这将涉及跟踪每个函数调用的开销。
堆栈相关的问题相对较少,因为堆栈分配的变量在函数 return 上被释放,而大堆栈本身在 goroutine 退出时被释放。它们 可以 发生,比如 goroutines 正在泄漏(即使你创建新的 goroutines 也永远不会退出),或者如果你在不退出的协程。但是堆上的问题更常见,因为堆上的东西直到 GC 才被释放(像标准 Pool
这样的工具可以帮助你 回收 堆分配延迟 GC 需求的项目)。
所以,实际上,我主要关注 Alloc 和 TotalAlloc 是否过度使用堆,如果堆栈 space 以某种方式成为问题,请查看堆栈编号(也许 check for unexpectedly many running goroutines ).
这些观察结果是特定于实现的(我正在看 go 1.4,不是 tip),而且我不是 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)
}