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

memstats 结构中的哪些字段仅指堆,仅指堆栈

memstats 结构中的哪些字段仅指堆,仅指堆栈

Go
炎炎设计 2021-09-13 14:55:19
Go 运行时有很多与堆和堆栈相关的不同变量,并且一些堆栈编号是堆编号的一部分,导致混淆(对我而言)。例如,在此链接中。它说// 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在运行时文档中(摘录如下),它给出了 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 回答

?
红糖糍粑

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 堆栈的信息。


查看完整回答
反对 回复 2021-09-13
?
交互式爱情

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)

}


查看完整回答
反对 回复 2021-09-13
  • 2 回答
  • 0 关注
  • 221 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信