golang pprof 实战
2019/04/02. Golang 36.1k+ 27

目录
前言
实验准备
获取“炸弹”
使用 pprof
排查 CPU 占用过高
排查内存占用过高
排查频繁内存回收
排查协程泄露
排查锁的争用
排查阻塞操作
思考题
最后
前言
如果要说在 golang 开发过程进行性能调优,pprof 一定是一个大杀器般的工具。但在网上找到的教程都偏向简略,难寻真的能应用于实战的教程。这也无可厚非,毕竟 pprof 是当程序占用资源异常时才需要启用的工具,而我相信大家的编码水平和排场问题的能力是足够高的,一般不会写出性能极度堪忧的程序,且即使发现有一些资源异常占用,也会通过排查代码快速定位,这也导致 pprof 需要上战场的机会少之又少。即使大家有心想学习使用 pprof,却也常常相忘于江湖。

既然如此,那我就送大家一个性能极度堪忧的“炸弹”程序吧!

这程序没啥正经用途缺极度占用资源,基本覆盖了常见的性能问题。本文就是希望读者能一步一步按照提示,使用 pprof 定位这个程序的的性能瓶颈所在,借此学习 pprof 工具的使用方法。

因此,本文是一场“实验课”而非“理论课”,请读者腾出时间,脚踏实地,一步一步随实验步骤进行操作,这会是一个很有趣的冒险,不会很无聊,希望你能喜欢。

实验准备
这里假设你有基本的 golang 开发功底,拥有 golang 开发环境并配置了 $GOPATH,能熟练阅读简单的代码或进行简单的修改,且知道如何编译运行 golang 程序。此外,需要你大致知道 pprof 是干什么的,有一个基本印象即可,你可以花几分钟时间读一下《Golang 大杀器之性能剖析 PProf》的开头部分,这不会耽误太久。

此外由于你需要运行一个“炸弹”程序,请务必确保你用于做实验的机器有充足的资源,你的机器至少需要:

2 核 CPU;
2G 内存。
注意,以上只是最低需求,你的机器配置能高于上述要求自然最好。实际运行“炸弹”时,你可以关闭电脑上其他不必要的程序,甚至 IDE 都不用开,我们的实验操作基本上是在命令行里进行的。

此外,务必确保你是在个人机器上运行“炸弹”的,能接受机器死机重启的后果(虽然这发生的概率很低)。请你务必不要在危险的边缘试探,比如在线上服务器运行这个程序。

可能说得你都有点害怕了,为打消你顾虑,我可以剧透一下“炸弹”的情况,让你安心:

程序会占用约 2G 内存;
程序占用 CPU 最高约 100%(总量按“核数 * 100%”来算);
程序不涉及网络或文件读写;
程序除了吃资源之外没有其他危险操作。
且程序所占用的各类资源,均不会随着运行时间的增长而增长,换句话说,只要你把“炸弹”启动并正常运行了一分钟,就基本确认安全了,之后即使运行几天也不会有更多的资源占用,除了有点费电之外。

获取“炸弹”
炸弹程序的代码我已经放到了 GitHub 上,你只需要在终端里运行 go get 便可获取,注意加上 -d 参数,避免下载后自动安装:

go get -d github.com/wolfogre/go-pprof-practice
cd $GOPATH/src/github.com/wolfogre/go-pprof-practice
我们可以简单看一下 main.go 文件,里面有几个帮助排除性能调问题的关键的的点,我加上了些注释方便你理解,如下:

package main

import (
    // 略
    _ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告
    // 略
)

func main() {
    // 略

    runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载
    runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
    runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪

    go func() {
        // 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了
        if err := http.ListenAndServe(":6060", nil); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }()

    // 略
}

除此之外的其他代码你一律不用看,那些都是我为了模拟一个“逻辑复杂”的程序而编造的,其中大多数的问题很容易通过肉眼发现,但我们需要做的是通过 pprof 来定位代码的问题,所以为了保证实验的趣味性请不要提前阅读代码,可以实验完成后再看。

接着我们需要编译一下这个程序并运行,你不用担心依赖问题,这个程序没有任何外部依赖。

go build
./go-pprof-practice
运行后注意查看一下资源是否吃紧,机器是否还能扛得住,坚持一分钟,如果确认没问题,咱们再进行下一步。

控制台里应该会不停的打印日志,都是一些“猫狗虎鼠在不停地吃喝拉撒”的屁话,没有意义,不用细看。

image

使用 pprof
保持程序运行,打开浏览器访问 http://localhost:6060/debug/pprof/,可以看到如下页面:

页面上展示了可用的程序运行采样数据,分别有:

类型 描述 备注
allocs 内存分配情况的采样信息 可以用浏览器打开,但可读性不高
blocks 阻塞操作情况的采样信息 可以用浏览器打开,但可读性不高
cmdline 显示程序启动命令及参数 可以用浏览器打开,这里会显示 ./go-pprof-practice
goroutine 当前所有协程的堆栈信息 可以用浏览器打开,但可读性不高
heap 堆上内存使用情况的采样信息 可以用浏览器打开,但可读性不高
mutex 锁争用情况的采样信息 可以用浏览器打开,但可读性不高
profile CPU 占用情况的采样信息 浏览器打开会下载文件
threadcreate 系统线程创建情况的采样信息 可以用浏览器打开,但可读性不高
trace 程序运行跟踪信息 浏览器打开会下载文件,本文不涉及,可另行参阅《深入浅出 Go trace》
因为 cmdline 没有什么实验价值,trace 与本文主题关系不大,threadcreate 涉及的情况偏复杂,所以这三个类型的采样信息这里暂且不提。除此之外,其他所有类型的采样信息本文都会涉及到,且炸弹程序已经为每一种类型的采样信息埋藏了一个对应的性能问题,等待你的发现。

由于直接阅读采样信息缺乏直观性,我们需要借助 go tool pprof 命令来排查问题,这个命令是 go 原生自带的,所以不用额外安装。

我们先不用完整地学习如何使用这个命令,毕竟那太枯燥了,我们一边实战一边学习。

以下正式开始。

排查 CPU 占用过高
我们首先通过活动监视器(或任务管理器、top 命令,取决于你的操作系统和你的喜好),查看一下炸弹程序的 CPU 占用:

image

可以看到 CPU 占用相当高,这显然是有问题的,我们使用 go tool pprof 来排场一下:

go tool pprof http://localhost:6060/debug/pprof/profile
等待一会儿后,进入一个交互式终端:

输入 top 命令,查看 CPU 占用较高的调用:

很明显,CPU 占用过高是 github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat 造成的。

注:为了保证实验节奏,关于图中 flat、flat%、sum%、cum、cum% 等参数的含义这里就不展开讲了,你可以先简单理解为数字越大占用情况越严重,之后可以在《Golang 大杀器之性能剖析 PProf》等其他资料中深入学习。

输入 list Eat,查看问题具体在代码的哪一个位置:

可以看到,是第 24 行那个一百亿次空循环占用了大量 CPU 时间,至此,问题定位成功!

接下来有一个扩展操作:图形化显示调用栈信息,这很酷,但是需要你事先在机器上安装 graphviz,大多数系统上可以轻松安装它:

brew install graphviz # for macos
apt install graphviz # for ubuntu
yum install graphviz # for centos

或者你也可以访问 graphviz 官网寻找适合自己操作系统的安装方法。

安装完成后,我们继续在上文的交互式终端里输入 web,注意,虽然这个命令的名字叫“web”,但它的实际行为是产生一个 .svg 文件,并调用你的系统里设置的默认打开 .svg 的程序打开它。如果你的系统里打开 .svg 的默认程序并不是浏览器(比如可能是你的代码编辑器),这时候你需要设置一下默认使用浏览器打开 .svg 文件,相信这难不倒你。

你应该可以看到:

图中,tiger.(*Tiger).Eat 函数的框特别大,箭头特别粗,pprof 生怕你不知道这个函数的 CPU 占用很高,这张图还包含了很多有趣且有价值的信息,你可以多看一会儿再继续。

至此,这一小节使用 pprof 定位 CPU 占用的实验就结束了,你需要输入 exit 退出 pprof 的交互式终端。

为了方便进行后面的实验,我们修复一下这个问题,不用太麻烦,注释掉相关代码即可:

func (t *Tiger) Eat() {
    log.Println(t.Name(), "eat")
    //loop := 10000000000
    //for i := 0; i < loop; i++ {
    //    // do nothing
    //}
}

之后修复问题的的方法都是注释掉相关的代码,不再赘述。你可能觉得这很粗暴,但要知道,这个实验的重点是如何使用 pprof 定位问题,我们不需要花太多时间在改代码上。

排查内存占用过高
重新编译炸弹程序,再次运行,可以看到 CPU 占用率已经下来了,但是内存的占用率仍然很高:

image

我们再次运行使用 pprof 命令,注意这次使用的 URL 的结尾是 heap:

go tool pprof http://localhost:6060/debug/pprof/heap
再一次使用 top、list 来定问问题代码:

可以看到这次出问题的地方在 github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal,函数内容如下:

func (m *Mouse) Steal() {
    log.Println(m.Name(), "steal")
    max := constant.Gi
    for len(m.buffer) * constant.Mi < max {
        m.buffer = append(m.buffer, [constant.Mi]byte{})
    }
}

可以看到,这里有个循环会一直向 m.buffer 里追加长度为 1 MiB 的数组,直到总容量到达 1 GiB 为止,且一直不释放这些内存,这就难怪会有这么高的内存占用了。

使用 web 来查看图形化展示,可以再次确认问题确实出在这里:

image

现在我们同样是注释掉相关代码来解决这个问题。

再次编译运行,查看内存占用:

image

可以看到内存占用已经将到了 35 MB,似乎内存的使用已经恢复正常,一片祥和。

但是,内存相关的性能问题真的已经全部解决了吗?

排查频繁内存回收
你应该知道,频繁的 GC 对 golang 程序性能的影响也是非常严重的。虽然现在这个炸弹程序内存使用量并不高,但这会不会是频繁 GC 之后的假象呢?

为了获取程序运行过程中 GC 日志,我们需要先退出炸弹程序,再在重新启动前赋予一个环境变量,同时为了避免其他日志的干扰,使用 grep 筛选出 GC 日志查看:

GODEBUG=gctrace=1 ./go-pprof-practice | grep gc
日志输出如下:

image

可以看到,GC 差不多每 3 秒就发生一次,且每次 GC 都会从 16MB 清理到几乎 0MB,说明程序在不断的申请内存再释放,这是高性能 golang 程序所不允许的。

如果你希望进一步了解 golang 的 GC 日志可以查看《如何监控 golang 程序的垃圾回收》,为保证实验节奏,这里不做展开。

所以接下来使用 pprof 排查时,我们在乎的不是什么地方在占用大量内存,而是什么地方在不停地申请内存,这两者是有区别的。

由于内存的申请与释放频度是需要一段时间来统计的,所有我们保证炸弹程序已经运行了几分钟之后,再运行命令:

go tool pprof http://localhost:6060/debug/pprof/allocs
同样使用 top、list、web 大法:

image

可以看到 github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run 会进行无意义的内存申请,而这个函数又会被频繁调用,这才导致程序不停地进行 GC:

func (d *Dog) Run() {
    log.Println(d.Name(), "run")
    _ = make([]byte, 16 * constant.Mi)
}

这里有个小插曲,你可尝试一下将 16 * constant.Mi 修改成一个较小的值,重新编译运行,会发现并不会引起频繁 GC,原因是在 golang 里,对象是使用堆内存还是栈内存,由编译器进行逃逸分析并决定,如果对象不会逃逸,便可在使用栈内存,但总有意外,就是对象的尺寸过大时,便不得不使用堆内存。所以这里设置申请 16 MiB 的内存就是为了避免编译器直接在栈上分配,如果那样得话就不会涉及到 GC 了。

我们同样注释掉问题代码,重新编译执行,可以看到这一次,程序的 GC 频度要低很多,以至于短时间内都看不到 GC 日志了:

image

排查协程泄露
由于 golang 自带内存回收,所以一般不会发生内存泄露。但凡事都有例外,在 golang 中,协程本身是可能泄露的,或者叫协程失控,进而导致内存泄露。

我们在浏览器里可以看到,此时程序的协程数已经多达 106 条:

虽然 106 条并不算多,但对于这样一个小程序来说,似乎还是不正常的。为求安心,我们再次是用 pprof 来排查一下:

go tool pprof http://localhost:6060/debug/pprof/goroutine
同样是 top、list、web 大法:

可能这次问题藏得比较隐晦,但仔细观察还是不难发现,问题在于 github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink 在不停地创建没有实际作用的协程:

func (w *Wolf) Drink() {
    log.Println(w.Name(), "drink")
    for i := 0; i < 10; i++ {
        go func() {
            time.Sleep(30 * time.Second)
        }()
    }
}

可以看到,Drink 函数每次会释放 10 个协程出去,每个协程会睡眠 30 秒再退出,而 Drink 函数又会被反复调用,这才导致大量协程泄露,试想一下,如果释放出的协程会永久阻塞,那么泄露的协程数便会持续增加,内存的占用也会持续增加,那迟早是会被操作系统杀死的。

我们注释掉问题代码,重新编译运行可以看到,协程数已经降到 4 条了:

image

排查锁的争用
到目前为止,我们已经解决这个炸弹程序的所有资源占用问题,但是事情还没有完,我们需要进一步排查那些会导致程序运行慢的性能问题,这些问题可能并不会导致资源占用,但会让程序效率低下,这同样是高性能程序所忌讳的。

我们首先想到的就是程序中是否有不合理的锁的争用,我们倒一倒,回头看看上一张图,虽然协程数已经降到 4 条,但还显示有一个 mutex 存在争用问题。

相信到这里,你已经触类旁通了,无需多言,开整。

go tool pprof http://localhost:6060/debug/pprof/mutex
同样是 top、list、web 大法:

可以看出来这问题出在 github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Howl。但要知道,在代码中使用锁是无可非议的,并不是所有的锁都会被标记有问题,我们看看这个有问题的锁那儿触雷了。

func (w *Wolf) Howl() {
    log.Println(w.Name(), "howl")

    m := &sync.Mutex{}
    m.Lock()
    go func() {
        time.Sleep(time.Second)
        m.Unlock()
    }()
    m.Lock()
}

可以看到,这个锁由主协程 Lock,并启动子协程去 Unlock,主协程会阻塞在第二次 Lock 这儿等待子协程完成任务,但由于子协程足足睡眠了一秒,导致主协程等待这个锁释放足足等了一秒钟。虽然这可能是实际的业务需要,逻辑上说得通,并不一定真的是性能瓶颈,但既然它出现在我写的“炸弹”里,就肯定不是什么“业务需要”啦。

所以,我们注释掉这段问题代码,重新编译执行,继续。

排查阻塞操作
好了,我们开始排查最后一个问题。

在程序中,除了锁的争用会导致阻塞之外,很多逻辑都会导致阻塞。

可以看到,这里仍有 2 个阻塞操作,虽然不一定是有问题的,但我们保证程序性能,我们还是要老老实实排查确认一下才对。

go tool pprof http://localhost:6060/debug/pprof/block
top、list、web,你懂得。

可以看到,阻塞操作位于 github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Pee:

func (c *Cat) Pee() {
    log.Println(c.Name(), "pee")

    <-time.After(time.Second)
}

你应该可以看懂,不同于睡眠一秒,这里是从一个 channel 里读数据时,发生了阻塞,直到这个 channel 在一秒后才有数据读出,这就导致程序阻塞了一秒而非睡眠了一秒。

这里有个疑点,就是上文中是可以看到有两个阻塞操作的,但这里只排查出了一个,我没有找到其准确原因,但怀疑另一个阻塞操作是程序监听端口提供 porof 查询时,涉及到 IO 操作发生了阻塞,即阻塞在对 HTTP 端口的监听上,但我没有进一步考证。

不管怎样,恭喜你完整地完成了这个实验。

思考题
另有一些问题,虽然比较重要,但碍于篇幅(其实是我偷懒),就以思考题的形式留给大家了。

每次进入交互式终端,都会提示“type ‘help’ for commands, ‘o’ for options”,你有尝试过查看有哪些命令和哪些选项吗?有重点了解一下“sample_index”这个选项吗?
关于内存的指标,有申请对象数、使用对象数、申请空间大小、使用空间大小,本文用的是什么指标?如何查看不同的指标?(提示:在内存实验中,试试查看、修改“sample_index”选项。)
你有听说过火焰图吗?要不要在试验中生成一下火焰图?
main 函数中的 runtime.SetMutexProfileFraction 和 runtime.SetBlockProfileRate 是如何影响指标采样的?它们的参数的含义是什么?
评论区有很多很有价值的提问,你有注意到吗?
最后
碍于我的水平有限,实验中还有很多奇怪的细节我只能暂时熟视无睹(比如“排查内存占用过高”一节中,为什么实际申请的是 1.5 GiB 内存),如果这些奇怪的细节你也发现了,并痛斥我假装睁眼瞎,那么我的目的就达到了……

——还记得吗,本文的目的是让你熟悉使用 pprof,消除对它的陌生感,并能借用它进一步得了解 golang。而你通过这次试验,发现了程序的很多行为不同于你以往的认知或假设,并抱着好奇心,开始向比深处更深处迈进,那么,我何尝不觉得这是本文的功德呢?

与君共勉。

27 Comments - powered by utteranc.es
@bluntdel
bluntdel commented on 2019年6月14日
感谢,对pprof有了最基本的了解和使用,但还是浅显了点,期待下篇。

@duncanwang
duncanwang commented on 2019年8月10日
超级棒,我注明出处,转载一下哦。

@yanjinbin
yanjinbin commented on 2019年11月7日
为什么我的web svg没有dog 函数分支啊

@wolfogre
wolfogre commented on 2019年11月7日
Owner
@yanjinbin 可以说明下是哪一步不符合预期吗?如果是“排查频繁内存回收”那一步,请确认下有没有让程序先运行几分钟再执行 go pporf,时间间隔过短的话可能从 profile 里看不出东西。

@yanjinbin
yanjinbin commented on 2019年11月7日
@yanjinbin 可以说明下是哪一步不符合预期吗?如果是“排查频繁内存回收”那一步,请确认下有没有让程序先运行几分钟再执行 go pporf,时间间隔过短的话可能从 profile 里看不出东西。

image
cpu内存过高 就这个红色标注的分支 我web生成不了
我的如下
image
我确实运行了4min左右,时间应该够了吧
环境信息:
go version go1.13.4 darwin/amd64

@yanjinbin
yanjinbin commented on 2019年11月7日
还有 火焰图🔥 跟这个pprof差不多吧 为什么uber的 go torch要说 1.11以上都整合到pprof上面去了呢 纳闷

@wolfogre
wolfogre commented on 2019年11月8日
Owner
@yanjinbin 这是正常现象,因为我写的代码只是靠疯狂循环来提升CPU占用率,并不能精准控制占用多少,况且 pprof 采样也会有一些误差,你可以看到我的 tiger 占用是 89.18%,而你的是 96.85%,而这个树形调用栈图只会显示占用较高的调用栈,不是所有的调用栈,那样的话图就没法看了,所以你的图里,dog 的 CPU 占用实际上是被认为太小了所以被忽略了。这不重要,重要的 tiger 被正确发现了。

所以火焰图的优点就在这里,它是另一种图形化展示调用栈资源占用的方式,不仅能突出资源占用的大户,对于占用量不高的调用栈也不会完全丢失细节,但可能没有树形调用栈图这么直白易懂。

@yanjinbin
yanjinbin commented on 2019年11月8日
@yanjinbin 这是正常现象,因为我写的代码只是靠疯狂循环来提升CPU占用率,并不能精准控制占用多少,况且 pprof 采样也会有一些误差,你可以看到我的 tiger 占用是 89.18%,而你的是 96.85%,而这个树形调用栈图只会显示占用较高的调用栈,不是所有的调用栈,那样的话图就没法看了,所以你的图里,dog 的 CPU 占用实际上是被认为太小了所以被忽略了。这不重要,重要的 tiger 被正确发现了。

所以火焰图的优点就在这里,它是另一种图形化展示调用栈资源占用的方式,不仅能突出资源占用的大户,对于占用量不高的调用栈也不会完全丢失细节,但可能没有树形调用栈图这么直白易懂。
嗯 谢谢,我现在想知道的就是 火焰图 到底有没有可以用1.13版本的pprof展现呢 之前是uber go torch项目

@yanjinbin
yanjinbin commented on 2019年11月8日
楼主 现在的火焰图 还是用uber的go torch吗 不是说整合到pprof上面去了?

@yanjinbin
yanjinbin commented on 2019年11月8日
1数据采样: go tool pprof pprof http://127.0.0.1:[端口号]/debug/pprof/profile -seconds [采样间隔时间数字]

go tool pprof pprof http://127.0.0.1:6060/debug/pprof/profile -seconds 10

2生成火焰图 : go tool pprof -http=:8081 ~/pprof/[文件路径名].pb.gz

@wolfogre
wolfogre commented on 2019年11月8日
Owner
@yanjinbin go-torch 已经废弃了,pprof 整合了火焰图这事儿我之前确实不知道。

刚试了一下你写的步骤,赞👍。不过有个笔误哈,你想说的应该是:

go tool pprof -seconds [采样间隔时间数字] http://127.0.0.1:[端口号]/debug/pprof/profile
@wolfogre
wolfogre commented on 2019年12月25日
Owner
来自 @wenchaopeng 于 issue #1 的提问:

pprof统计的内存与PC上显示的内存不符合
你好,看了你写的关于pprof的博客,写的很好,不过有一个问题,你那个炸弹程序占用内存大概2GB,我在linux下跑了,通过htop查看,确实在2GB左右,但是通过pprof查看,只能查到1GB内存占用,请教一下这是为什么,两边内存对不上,请解惑!

好问题哈。是这样的,代码中一共有两处有意消耗内存,一处是这里:

func (m *Mouse) Steal() {
log.Println(m.Name(), “steal”)
max := constant.Gi
for len(m.buffer) * constant.Mi < max {
m.buffer = append(m.buffer, [constant.Mi]byte{})
}
}
可以看到这里的逻辑是占上不超过 1G 内存,所以 pprof 查到 1G 内存是符合预期的。

另一处是这里:

func (d *Dog) Run() {
log.Println(d.Name(), “run”)
_ = make([]byte, 16 * constant.Mi)
}
这里故意申请 16M 内存并立即丢弃,这里本意是为了模拟一个频繁 GC 问题,因为上文已经占了 1G 内存不放,所以 golang 每次 GC 结束,会发现还占用了 1G,所以 GC 策略会在下次内存占用超过 1G * 2 时(具体为什么是两倍可以看下 GC 策略相关资料)再次触发 GC 降回 1G,所以站在操作系统来看,这个程序的内存占用是在 1GB - 2GB 之间的(注意 GC 掉的内存并不一定会立即返还给操作系统),而我说 “程序会占用约 2G 内存” 是说了最坏情况,意思是你至少得有 2G 内存。

然后你应该意识到一个问题了,pprof 看到的内存占用,其实只是 golang 逻辑上正在使用的内存量,不包括已被 GC 回收但尚未返还给操作系统的内存,同样也不包括内核态的内存占用。而 htop 是站在操作系统层面看到的进程内存占用,理论上就是会比 pprof 看到的内存占用量更多的。

如果你在实际工作中发现 htop 看到内存占用贼大,pprof 看到内存占用却不多,就要考虑下,是不是有大量内存被 GC 但还没来得及返还给操作系统,是不是某些内核态操作(比如 IO)消耗了大量内存。

@lieinsun
lieinsun commented on 2020年1月7日
哇哦,超级棒的文章,教程向的博客一顿好找啊

@wdd817
wdd817 commented on 2020年3月16日
讲道理, 粗浅但是特别棒的入门文章, 👍

@Tracy-Hu
Tracy-Hu commented on 2020年8月4日
优质文章o( ̄▽ ̄)d关于排查阻塞部分,我按照步骤一步步到这里后,block数量显示为1而不是文中的2啦,所以小编最后的疑点是不是没问题的~另外,web生成的图,里面的虚线代表什么意思呀?麻烦小编帮忙解答下Thanks♪(・ω・)ノ

@SeasonWoo
SeasonWoo commented on 2021年8月3日
写的很棒,对于入门真的特别有帮助!

@kevinx1
kevinx1 commented on 2021年9月1日
很全面,学习中!

@chaggle
chaggle commented on 2022年3月9日
好耶!

@jieyuefeng
jieyuefeng commented on 2022年3月12日
你好,文中《Golang 大杀器之性能剖析 PProf》链接更新到了https://github.com/eddycjy/blog/blob/master/content/posts/go/tools/2018-09-15-go-tool-pprof.md

@lyfwfm
lyfwfm commented on 2022年5月21日
请问该方法能在生产环境使用吗?我担心pprof连上生产环境进程(因为进程此时已经cpu和内存很高了),导致进程直接挂掉。

@wolfogre
wolfogre commented on 2022年5月22日
Owner
请问该方法能在生产环境使用吗?我担心pprof连上生产环境进程(因为进程此时已经cpu和内存很高了),导致进程直接挂掉。

@lyfwfm 可以参考这篇文章:Continuous Profiling of Go programs

@MoneyHappy
MoneyHappy commented on 2023年4月9日
请教下,有这么个情景:可能应用程序是在一台打包机打好,然后二进制文件传到另一台机器运行,这时 list 就无法查看代码信息了,因为找不到代码位置了。 请问下这个一般有什么解决办法么

@wolfogre
wolfogre commented on 2023年4月10日
Owner
请教下,有这么个情景:可能应用程序是在一台打包机打好,然后二进制文件传到另一台机器运行,这时 list 就无法查看代码信息了,因为找不到代码位置了。 请问下这个一般有什么解决办法么

编译的时候加了 -ldflags “-s -w”,去掉了调试信息?

@yy57
yy57 commented on 2023年6月16日
牛!这个是真的牛!看完就知道怎么实操了

@mxisc
mxisc commented on 2023年7月18日
很棒的一篇文章,为防止后续因为某些因素该贴无法访问,我进行了转载,并且已经表明了转载出处,供后续自己学习使用

@wxsms
wxsms commented on 2023年9月8日
非常棒的文章!感谢分享!

@haohui03
haohui03 commented on 2023年11月9日
写的很好,学习了!!!!

作者:秦晓川  创建时间:2024-06-11 20:25
最后编辑:秦晓川  更新时间:2024-08-16 00:22
上一篇:
下一篇: