我有一个Go程序,该程序可以计算内存中的大型相关矩阵。为此,我建立了一个包含3个goroutine的管道,其中第一个读取文件,第二个计算相关矩阵,最后一个将结果存储到磁盘。
问题是,当我运行程序时,Go运行时分配了约17GB的内存,而矩阵仅占用了约2-3GB。使用runtime.ReadMemStats
表示程序正在使用〜17GB(并通过htop进行了验证),但是pprof
仅报告了大约2.3GB。
如果我通过管道运行一个文件后查看内存状态:
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
fmt.Printf("Total alloc: %d GB\n", mem.Alloc/1000/1000/1000)
这显示了程序的总分配:
Total alloc: 17 GB
但是,如果我运行go tool pprof mem.prof
,则会得到以下结果:
(pprof) top5
Showing nodes accounting for 2.21GB, 100% of 2.21GB total
Showing top 5 nodes out of 9
flat flat% sum% cum cum%
1.20GB 54.07% 54.07% 1.20GB 54.07% dataset.(*Dataset).CalcCorrelationMatrix
1.02GB 45.93% 100% 1.02GB 45.93% bytes.makeSlice
0 0% 100% 1.02GB 45.93% bytes.(*Buffer).WriteByte
0 0% 100% 1.02GB 45.93% bytes.(*Buffer).grow
0 0% 100% 1.02GB 45.93% encoding/json.Indent
所以我想知道如何才能找出峰值内存使用量仅为2.5GB时,为什么程序会分配17 GB? 是否可以使用pprof跟踪整个程序的内存使用情况?
编辑
我再次使用GODEBUG = gctrace = 1运行了程序,并获得了以下跟踪信息:
gc 1 @0.017s 0%: 0.005+0.55+0.003 ms clock, 0.022+0/0.47/0.11+0.012 ms cpu, 1227->1227->1226 MB, 1228 MB goal, 4 P
gc 2 @14.849s 0%: 0.003+1.7+0.004 ms clock, 0.015+0/1.6/0.11+0.018 ms cpu, 1227->1227->1227 MB, 2452 MB goal, 4 P
gc 3 @16.850s 0%: 0.006+60+0.003 ms clock, 0.027+0/0.46/59+0.015 ms cpu, 1876->1876->1712 MB, 2455 MB goal, 4 P
gc 4 @22.861s 0%: 0.005+238+0.003 ms clock, 0.021+0/0.46/237+0.015 ms cpu, 3657->3657->3171 MB, 3658 MB goal, 4 P
gc 5 @30.716s 0%: 0.005+476+0.004 ms clock, 0.022+0/0.44/476+0.017 ms cpu, 5764->5764->5116 MB, 6342 MB goal, 4 P
gc 6 @46.023s 0%: 0.005+949+0.004 ms clock, 0.020+0/0.47/949+0.017 ms cpu, 10302->10302->9005 MB, 10303 MB goal, 4 P
gc 7 @64.878s 0%: 0.006+382+0.004 ms clock, 0.024+0/0.46/382+0.019 ms cpu, 16548->16548->7728 MB, 18011 MB goal, 4 P
gc 8 @89.774s 0%: 0.86+2805+0.006 ms clock, 3.4+0/24/2784+0.025 ms cpu, 20208->20208->17088 MB, 20209 MB goal, 4 P
因此,很明显堆在整个程序中稳定增长,但是我无法查明位置。在调用占用大量内存的函数之后,我已经使用pprof.WriteHeapProfile
来配置内存使用情况:
func memoryProfile(profpath string) {
if _, err := os.Stat(profpath); os.IsNotExist(err) {
os.Mkdir(profpath, os.ModePerm)
}
f, err := os.Create(path.Join(profpath, "mem.mprof"))
fmt.Printf("Creating memory profile in %s", "data/profile/mem.mprof\n")
if err != nil {
panic(err)
}
if err := pprof.WriteHeapProfile(f); err != nil {
panic(err)
}
f.Close()
}
答案 0 :(得分:1)
如JimB的注释中所述,go配置文件是一个采样分析器,并以一定间隔对内存使用情况进行采样。在我的情况下,采样的频率不足以捕获使用大量内存的函数(JSON编组)。
通过设置环境变量来提高分析器的采样率
$ export GODEBUG=memprofilerate=1
将更新runtime.MemProfileRate
,并且配置文件现在包括每个分配的块。
答案 1 :(得分:0)
一种可能的解决方案(例如我的情况)是使用-race
编译二进制文件,从而可以检查竞争条件。
此操作的开销是巨大,如果使用htop
或类似方法进行检查,则看起来像是大量内存泄漏,但不会显示在任何pprof输出中