golang性能优化

werbenhu / 109 / 2023-09-25 15:36:04

ChatGPT 可用网址,仅供交流学习使用,如对您有所帮助,请收藏并推荐给需要的朋友。
https://ckai.xyz

1.GODEBUG命令运行程序,跟踪内存信息

命令GODEBUG='gctrace=1' ./snippet_mem用于启用Go程序的调试和性能分析。当你设置 GODEBUG='gctrace=1' 启用 gctrace 时,Go程序将输出与垃圾收集器相关的调试信息。这对于分析程序的内存管理和性能问题非常有用。

输出信息:

gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P

含义:

    gc #        GC次数的编号,每次GC时递增
    @#s         距离程序开始执行时的时间
    #%          GC占用的执行时间百分比
    #+...+#     GC使用的时间
    #->#-># MB  GC开始,结束,以及当前活跃堆内存的大小,单位M
    # MB goal   全局堆内存大小
    # P         使用processor的数量

2. runtime代码打印内存信息

2.1runtime.MemStats

也可以利用 runtime库里的ReadMemStats()方法, 读取的内存信息会保存在runtime.MemStats这个结构体中,可以从这个结构体中获取当前的内存信息,

// MemStats 记录有关内存分配器的统计信息。
type MemStats struct {
    // 通用统计信息。

    // Alloc 表示分配的堆对象的字节数。
    //
    // 这与HeapAlloc(见下文)相同。
    Alloc uint64

    // TotalAlloc 表示为堆对象累积分配的字节数。
    //
    // TotalAlloc 随着堆对象的分配而增加,但
    // 与Alloc和HeapAlloc不同,当
    // 对象被释放时,它不会减少。
    TotalAlloc uint64

    // Sys 表示从操作系统获取的内存总字节数。
    //
    // Sys 是下面的XSys字段的总和。Sys度量
    // 用于Go运行时的堆、栈和其他内部数据结构的虚拟地址空间。
    // 在任何给定时刻,可能并非所有虚拟地址空间都由物理内存支持,
    // 尽管总体上它曾经都是。
    Sys uint64

    // Lookups 是运行时执行的指针查找次数。
    //
    // 这主要用于调试运行时内部。
    Lookups uint64

    // Mallocs 是累积的堆对象分配计数。
    // 存活对象的数量是Mallocs - Frees。
    Mallocs uint64

    // Frees 是累积的堆对象释放计数。
    Frees uint64

    // 堆内存统计信息。
    // HeapAlloc 表示分配的堆对象的字节数。
    //
    // “已分配”的堆对象包括所有可达对象,
    // 以及垃圾收集器尚未释放的不可达对象。
    // 具体来说,HeapAlloc随着堆对象的分配而增加,
    // 随着堆被扫描和不可达对象被释放而减少。
    // 扫描在GC周期之间逐增地进行,因此这两个过程
    // 同时进行,因此HeapAlloc倾向于平滑变化
    // (与停止-世界垃圾收集器的典型锯齿形相比)。
    HeapAlloc uint64

    // HeapSys 表示从操作系统获取的堆内存字节数。
    //
    // HeapSys度量堆的虚拟地址空间的数量
    // 为堆保留。这包括虚拟地址空间
    // 已经保留但尚未使用,不消耗物理内存,
    // 但通常很小,以及虚拟地址空间,其中
    // 物理内存在变得未使用后已经返回给了OS
    // (请参见HeapReleased以获取后者的度量)。
    //
    // HeapSys估计了堆的最大大小。
    HeapSys uint64

    // HeapIdle 表示空闲(未使用)span中的字节数。
    //
    // 空闲span中不包含任何对象。这些span可以
    // (并且可能已经)返回给操作系统,或者可以
    // 用于堆分配,或者可以重新用于
    // 堆外存储器。
    //
    // HeapIdle减去HeapReleased估计了内存量
    // 可以返回给操作系统,但由于运行时保留
    // 可以增大堆而不需要从OS请求更多内存。
    // 如果这个差异明显大于堆大小,这表明最近
    // 存活堆大小的短暂峰值。
    HeapIdle uint64

    // HeapInuse 表示使用中span中的字节数。
    //
    // 使用中span至少包含一个对象。这些span
    // 只能用于大致相同大小的其他对象。
    //
    // HeapInuse减去HeapAlloc估计了内存量
    // 已分配给特定大小类,但当前未使用。
    // 这是对碎片的上限,但通常可以有效地重用
    // 这段内存。
    HeapInuse uint64

    // HeapReleased 表示返回给操作系统的物理内存字节数。
    //
    // 这计算了从已经返回给操作系统的空闲span中的堆内存。
    HeapReleased uint64

    // HeapObjects 是已分配的堆对象数量。
    //
    // 与HeapAlloc一样,这会随着对象的分配而增加,
    // 随着堆的扫描和不可达对象的释放而减少。
    HeapObjects uint64

    // 栈内存统计信息。
    //
    // 堆栈不被视为堆的一部分,但运行时
    // 可以重新使用堆内存的span来进行堆栈内存,反之亦然。

    // StackInuse 表示使用中的stack span中的字节数。
    //
    // 使用中的stack span至少包含一个stack。这些
    // span只能用于相同大小的其他stack。
    //
    // 不存在StackIdle,因为未使用的stack span
    // 返回到堆(因此计入HeapIdle)。
    StackInuse uint64

    // StackSys 表示从操作系统获取的stack内存字节数。
    //
    // StackSys是StackInuse,加上直接从操作系统获得的任何内存
    // 用于OS线程堆栈。
    //
    // 在非cgo程序中,此度量标准目前等于StackInuse
    // (但不应依赖,该值可能在将来更改)。
    //
    // 在cgo程序中,此度量标准包括直接从操作系统分配的OS线程堆栈。
    // 目前,这仅在c-shared和c-archive构建模式下占用一堆的内存,
    // 并且来自OS的其他堆栈(尤其是由C代码分配的)目前不会被测量。
    // 请注意,这也可能会在将来更改。
    StackSys uint64

    // 堆外存储器统计信息。
    //
    // 以下统计信息测量了运行时内部结构
    // 这些结构不是从堆内存分配的(通常不是这样,
    // 因为它们是堆的一部分的实现)。与
    // 堆或堆内存不同,分配给这些
    // 结构的内存是专门用于这些结构的。
    //
    // 这些主要用于调试运行时内存开销。

    // MSpanInuse 表示分配的mspan结构的字节数。
    MSpanInuse uint64

    // MSpanSys 表示从操作系统获取的mspan结构的内存字节数。
    MSpanSys uint64

    // MCacheInuse 表示分配的mcache结构的字节数。
    MCacheInuse uint64

    // MCacheSys 表示从操作系统获取的mcache结构的内存字节数。
    MCacheSys uint64

    // BuckHashSys 表示分布桶哈希表中的内存字节数。
    BuckHashSys uint64

    // GCSys 表示垃圾回收元数据中的内存字节数。
    GCSys uint64

    // OtherSys 表示杂项堆外运行时分配的内存字节数。
    OtherSys uint64

    // 垃圾收集器统计信息。

    // NextGC 是下一个GC周期的目标堆大小。
    //
    // 垃圾回收器的目标是使HeapAlloc ≤ NextGC。
    // 在每个GC周期结束时,根据可达数据的数量和
    // GOGC的值来计算下一个周期的目标。
    NextGC uint64

    // LastGC 是上一个垃圾回收完成的时间,以
    // 纳秒为单位自1970年以来的时间(UNIX纪元)。
    LastGC uint64

    // PauseTotalNs 表示自程序启动以来GC的
    // 停止-世界暂停的累积纳秒数。
    //
    // 在停止-世界暂停期间,所有goroutine都被暂停
    // 只有垃圾回收器可以运行。
    PauseTotalNs uint64

    // PauseNs 是最近GC停止-世界暂停时间的循环缓冲区
    // 以纳秒为单位。
    //
    // 最近的暂停位于PauseNs[(NumGC+255)%256]。
    // 通常,PauseNs[N%256]记录了在最
    // 近的N%256th GC周期中暂停的时间。在一个周期中
    // 可能有多次暂停;这是一个循环缓冲区中所有暂停的总和。
    PauseNs [256]uint64

    // PauseEnd 是最近GC暂停结束时间的循环缓冲区,
    // 以纳秒为单位自1970年以来的时间(UNIX纪元)。
    //
    // 这个缓冲区的填充方式与PauseNs相同。
    // 在一个周期中可能有多次暂停;这记录了
    // 在一个周期中最后一个暂停的结束。
    PauseEnd [256]uint64

    // NumGC 是已完成的GC周期数。
    NumGC uint32

    // NumForcedGC 是应用程序调用GC函数强制进行的GC周期数。
    NumForcedGC uint32

    // GCCPUFraction 是自程序启动以来
    // GC使用的可用CPU时间的分数。
    //
    // GCCPUFraction表示为0到1之间的数字,
    // 其中0表示GC没有使用该程序的CPU。程序的
    // 可用CPU时间定义为自程序启动以来的GOMAXPROCS积分。
    // 也就是说,如果GOMAXPROCS为2,并且程序运行了
    // 10秒钟,那么它的“可用CPU”为20秒。GCCPUFraction
    // 不包括用于写入屏障活动的CPU时间。
    //
    // 这与GODEBUG=gctrace=1报告的CPU时间相同。
    GCCPUFraction float64

    // EnableGC 表示GC是否已启用。它始终为true,
    // 即使GOGC=off。
    EnableGC bool

    //...
}

2.2 示例代码

package main

import (
    "log"
    "runtime"
    "time"
)

func readMemStats() {

    var ms runtime.MemStats

    runtime.ReadMemStats(&ms)

    log.Printf(" ===> Alloc:%d(bytes) HeapIdle:%d(bytes) HeapReleased:%d(bytes)", ms.Alloc, ms.HeapIdle, ms.HeapReleased)
}

func test() {
    //slice 会动态扩容,用slice来做堆内存申请
    container := make([]int, 8)

    log.Println(" ===> loop begin.")
    for i := 0; i < 32*1000*1000; i++ {
        container = append(container, i)
        if ( i == 16*1000*1000) {
            readMemStats()
        }
    }

    log.Println(" ===> loop end.")
}

func main() {
    log.Println(" ===> [Start].")

    readMemStats()
    test()
    readMemStats()

    log.Println(" ===> [force gc].")
    runtime.GC() //强制调用gc回收

    log.Println(" ===> [Done].")
    readMemStats()

    go func() {
        for {
            readMemStats()
            time.Sleep(10 * time.Second)
        }
    }()

    time.Sleep(3600 * time.Second) //睡眠,保持程序不退出
}

可以看到,打印[Done].之后那条trace信息,Alloc已经下降,即内存已被垃圾回收器回收。在2020/03/02 18:21:38和2020/03/02 18:21:48的两条trace信息中,HeapReleased开始上升,即垃圾回收器把内存归还给系统。

3. pprof工具收集性能指标

你可以使用 pprof 包来查看程序的内存情况。pprof 包提供了用于性能分析的工具,包括查看内存分配情况的功能。pprof有两种方式获取程序运行数据的工具,分别对应两个标准库:

runtime/pprof: 采集工具型应用运行数据进行分析
net/http/pprof: 采集服务型应用运行时数据进行分析

// 如果要定制一些采集信息可以通过runtime开启
runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪,block  
runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪,mutex

3.1 服务型应用使用net/http/pprof

  • 导入 net/http/pprof 包以及 net/http 包,以便启动一个HTTP服务器来提供 pprof 的 Web 接口。
import (
    _ "net/http/pprof"
    "net/http"
)
  • 在你的代码中添加一个HTTP服务器,以便能够通过Web接口访问 pprof 数据

    go func() {
      // 监听HTTP请求,启动一个HTTP服务器
      // 注意:这是一个示例,你可以根据需要更改端口
      http.ListenAndServe("localhost:6060", nil)
    }()
  • 服务型的在浏览器中输入地址:http://127.0.0.1:6060/debug/pprof/,即可看到监控的数据。

3.2 工具型应用使用runtime/pprof

  • 在你的代码中的适当位置导入 runtime/pprof 包并使用它来生成内存分析数据。
import (
    "runtime/pprof"
    "os"
)

func main() {
    // 创建一个文件,用于存储内存分析数据
    memFile, _ := os.Create("memory.pprof")
    defer memFile.Close()

    // 开始内存分析,将结果写入文件
    pprof.WriteHeapProfile(memFile)

    // 你的应用程序代码
}

3.3 分析profile

3.3.1 工具型模式

执行go tool pprof app.profile ,会进入一个命令行交互页面,可以输入命令查看需要查看的信息.

3.3.2 服务端模式

  • 方式一:
    直接使用web服务接口数据
    go tool pprof http:127.0.0.1:6060/debug/pprof/profile?seconds=30
  • 方式二:
    先从web服务接口下载profile文件
    wget -O app.profile 'http:127.0.0.1:6060/debug/pprof/profile?seconds=30'
    然后分析下载profile文件,执行go tool pprof app.profile ,会进入一个命令行交互页面。

3.3.3 交互命令

执行go tool pprof app.profile后,可以通过help来看支持的命令,比如输入help可以看到有top命令,通过输入top来查看cpu的性能情况.

//flat:当前函数占用CPU的耗时
//flat::当前函数占用CPU的耗时百分比
//sun%:函数占用CPU的耗时累计百分比
//cum:当前函数加上调用当前函数的函数占用CPU的总耗时
//cum%:当前函数加上调用当前函数的函数占用CPU的总耗时百分比
//最后一列:函数名称
(pprof) top
Showing nodes accounting for 17.57s, 92.86% of 18.92s total
Dropped 147 nodes (cum <= 0.09s)
Showing top 10 nodes out of 48
      flat  flat%   sum%        cum   cum%
    15.62s 82.56% 82.56%     15.64s 82.66%  runtime.cgocall
     0.38s  2.01% 84.57%      0.48s  2.54%  math/rand.(*Rand).Int31n
     0.29s  1.53% 86.10%     16.52s 87.32%  internal/poll.(*FD).writeConsole
     0.25s  1.32% 87.42%      0.25s  1.32%  runtime.unlock2
     0.24s  1.27% 88.69%      0.35s  1.85%  bytes.(*Buffer).Write
     0.20s  1.06% 89.75%      0.31s  1.64%  unicode/utf16.Encode
     0.19s  1.00% 90.75%      0.19s  1.00%  runtime._ExternalCode
     0.16s  0.85% 91.60%      0.79s  4.18%  math/rand.Intn
     0.13s  0.69% 92.28%      0.19s  1.00%  runtime.lock2
     0.11s  0.58% 92.86%      0.11s  0.58%  runtime.stdcall2
(pprof)

4. 使用graphviz可视化

首先要下载graphviz, https://graphviz.org/download/ 安装对应的版本。

方式一: 这时候使用命令go tool pprof app.profile, 输入web指令,就可以查看对应的其可视化的分析结果了。

方式二:直接通过命令go tool pprof -http=:8080 app.profile启动web在浏览器中可视化查看结果。


作者
werbenhu
许可协议
CC BY 4.0
发布于
2023-09-25
修改于
2024-05-28
Bonnie image
尚未登录