起因
被同事灵魂拷问:图中这块空白是什么东西?
豆包回答说是数据采样不完整,特定函数或代码段未被调用之类的原因,感觉都不太合理。
之前看过一篇文章说:Heap Profiling的采样是无时无刻不在发生的,执行一次profiling仅仅是dump一下迄今为止的数据快照。这篇文章更加推翻了前面"数据采样不完整"的假设。那火焰图中的空白到底是啥,我们亲自试一下。
实验
实验设计
在一个函数中调用另外的函数,在多种位置申请内存,查看火焰图的空白情况。
前置准备
- 安装环境
- 编写测试代码
- 在Goland中运行测试代码,可以通过 pprof 的 HTTP 接口访问
http://localhost:8000/debug/pprof/heap?debug=1
- 在终端中执行以下命令,在页面中展示火焰图。其中,
-http=":8081"
表示用于查看火焰图的端口,http://localhost:8000/debug/pprof/heap
表示采集的数据源。需要注意,每次运行此命令时,生成的是当前堆快照,如果代码有变更,或者想要获取最新结果,需要中断后重新运行此命令。
go tool pprof -http=":8081" http://localhost:8000/debug/pprof/heap
- 火焰图分析方法:选择VIEW - Flame Graph (old)后,再选择SAMPLE - alloc_space。我们主要看这个,其它的选项也可以参考。
函数调用关系
func allocate1() {
allocate2()
allocate3()
}
代码1:allocate1本身占用空间
go
package main
import (
"net/http"
_ "net/http/pprof"
"time"
)
func allocate1() {
var s []string
for i := 0; i < 10000; i++ {
// allocate1占用的空间
for i := 0; i < 10; i++ {
s = append(s, "This is a sample string")
}
// allocate2占用的空间
allocate2()
// allocate3占用的空间
allocate3()
}
}
func allocate2() {
var s []string
for i := 0; i < 10; i++ {
s = append(s, "This is a sample string")
}
}
func allocate3() {
var s []string
for i := 0; i < 10; i++ {
s = append(s, "This is a sample string")
}
}
func main() {
go func() {
for {
allocate1()
time.Sleep(1 * time.Second)
}
}()
http.ListenAndServe(":8000", nil)
}
生成的火焰图:
代码2:allocate本身不占空间
把13-15行注释掉,让allocate1不占空间
go
package main
import (
"net/http"
_ "net/http/pprof"
"time"
)
func allocate1() {
//var s []string
for i := 0; i < 10000; i++ {
// allocate1占用的空间
//for i := 0; i < 10; i++ {
// s = append(s, "This is a sample string")
//}
// allocate2占用的空间
allocate2()
// allocate3占用的空间
allocate3()
}
}
func allocate2() {
var s []string
for i := 0; i < 10; i++ {
s = append(s, "This is a sample string")
}
}
func allocate3() {
var s []string
for i := 0; i < 10; i++ {
s = append(s, "This is a sample string")
}
}
func main() {
go func() {
for {
allocate1()
time.Sleep(1 * time.Second)
}
}()
http.ListenAndServe(":8000", nil)
}
生成的火焰图:
结论
当allocate1本身占用空间的时候,火焰图中的空白存在。当allocate1本身不占用空间的时候,火焰图被填满,不存在空白。说明空白的部分是上一级函数内部对象占用的空间。