memstats struct中的哪些字段仅指堆,只指堆栈

时间:2015-01-30 21:05:08

标签: go

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

2 个答案:

答案 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)
}