英文:
How to analyze golang memory?
问题
我写了一个使用1.2GB内存的golang程序。
调用go tool pprof http://10.10.58.118:8601/debug/pprof/heap
会生成一个只有323.4MB堆使用量的转储文件。
- 剩下的内存使用情况如何?
- 有没有更好的工具来解释golang运行时的内存使用情况?
使用gcvis
我得到了这个结果:
..以及这个堆形式的分析结果:
这是我的代码:https://github.com/sharewind/push-server/blob/v3/broker
英文:
I wrote a golang program, that uses 1.2GB of memory at runtime.
Calling go tool pprof http://10.10.58.118:8601/debug/pprof/heap
results in a dump with only 323.4MB heap usage.
- What's about the rest of the memory usage?
- Is there any better tool to explain golang runtime memory?
Using gcvis
I get this:
.. and this heap form profile:
Here is my code: https://github.com/sharewind/push-server/blob/v3/broker
答案1
得分: 86
堆剖析显示活动内存,即运行时认为 Go 程序正在使用的内存(即尚未被垃圾回收器回收)。当垃圾回收器回收内存时,剖析会缩小,但不会将内存返回给系统。在请求更多内存之前,您未来的分配将尝试使用先前收集的对象池中的内存。
从外部来看,这意味着您的程序的内存使用量要么增加,要么保持不变。外部系统呈现的程序的“Resident Size”是分配给程序的字节数,无论它是保留正在使用的 Go 值还是已收集的值。
这两个数字通常相差很大的原因是:
- 垃圾回收器回收内存对程序的外部视图没有影响
- 内存碎片化
- 垃圾回收器仅在使用的内存是前一个垃圾回收之后使用的内存的两倍时运行(默认情况下,请参阅:http://golang.org/pkg/runtime/#pkg-overview)
如果您想要准确了解 Go 如何查看内存,可以使用 runtime.ReadMemStats 调用:http://golang.org/pkg/runtime/#ReadMemStats
或者,由于您正在使用基于 Web 的剖析,如果可以通过浏览器访问剖析数据,请在 http://10.10.58.118:8601/debug/pprof/
上点击堆链接,将显示堆剖析的调试视图,底部有一个 runtime.MemStats 结构的打印输出。
runtime.MemStats 文档(http://golang.org/pkg/runtime/#MemStats)对所有字段进行了解释,但对于本讨论而言,有趣的字段是:
- HeapAlloc:基本上是剖析器提供的内容(活动堆内存)
- Alloc:类似于 HeapAlloc,但适用于所有 Go 管理的内存
- Sys:从操作系统请求的总内存量(地址空间)
Sys 和操作系统报告之间仍然会存在差异,因为 Go 向系统请求的内容与操作系统提供的内容并不总是相同的。此外,Go 不会跟踪 CGO / syscall(例如:malloc / mmap)内存。
英文:
The heap profile shows active memory, memory the runtime believes is in use by the go program (ie: hasn't been collected by the garbage collector). When the GC does collect memory the profile shrinks, but no memory is returned to the system. Your future allocations will try to use memory from the pool of previously collected objects before asking the system for more.
From the outside, this means that your program's memory use will either be increasing, or staying level. What the outside system presents as the "Resident Size" of your program is the number of bytes of RAM is assigned to your program whether it's holding in-use go values or collected ones.
The reason why these two numbers are often quite different are because:
- The GC collecting memory has no effect on the outside view of the program
- Memory fragmentation
- The GC only runs when the memory in use doubles the memory in use after the previous GC (by default, see: http://golang.org/pkg/runtime/#pkg-overview)
If you want an accurate breakdown of how Go sees the memory you can use the runtime.ReadMemStats call: http://golang.org/pkg/runtime/#ReadMemStats
Alternatively, since you are using web-based profiling if you can access the profiling data through your browser at: http://10.10.58.118:8601/debug/pprof/
, clicking the heap link will show you the debugging view of the heap profile, which has a printout of a runtime.MemStats structure at the bottom.
The runtime.MemStats documentation (http://golang.org/pkg/runtime/#MemStats) has the explanation of all the fields, but the interesting ones for this discussion are:
- HeapAlloc: essentially what the profiler is giving you (active heap memory)
- Alloc: similar to HeapAlloc, but for all go managed memory
- Sys: the total amount of memory (address space) requested from the OS
There will still be discrepancies between Sys, and what the OS reports because what Go asks of the system, and what the OS gives it are not always the same. Also CGO / syscall (eg: malloc / mmap) memory is not tracked by go.
答案2
得分: 41
作为对@Cookie of Nine回答的补充,简而言之:你可以尝试使用--alloc_space
选项。
go tool pprof
默认使用--inuse_space
。它对内存使用进行采样,因此结果是实际内存使用的子集。
通过使用--alloc_space
,pprof将返回自程序启动以来分配的所有内存。
英文:
As an addition to @Cookie of Nine's answer, in short: you can try the --alloc_space
option.
go tool pprof
use --inuse_space
by default. It samples memory usage so the result is subset of real one.
By --alloc_space
pprof returns all alloced memory since program started.
答案3
得分: 31
UPD(2022)
对于那些懂俄语的人,我做了一个演讲,并在这个主题上写了几篇文章:
原始答案(2017)
我一直对我的Go应用程序的内存使用量增长感到困惑,最后我不得不学习Go生态系统中的性能分析工具。运行时提供了许多指标,存储在runtime.Memstats结构中,但很难理解其中哪些指标可以帮助找出内存增长的原因,因此需要一些额外的工具。
性能分析环境
在你的应用程序中使用https://github.com/tevjef/go-runtime-metrics。例如,你可以将以下内容放在你的main
函数中:
import(
metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
//...
metrics.DefaultConfig.CollectionInterval = time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
// 处理错误
}
}
在Docker
容器中运行InfluxDB
和Grafana
:
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0
在Grafana
和InfluxDB
之间建立交互(Grafana主页->左上角->数据源->添加新数据源):
从https://grafana.com导入仪表板#3242(Grafana主页->左上角->仪表板->导入):
最后,启动你的应用程序:它将把运行时指标传输到容器化的Influxdb
。给你的应用程序施加合理的负载(在我的情况下,是相当小的负载-每秒5个请求数持续几个小时)。
内存消耗分析
Sys
(与RSS
同义)曲线与HeapSys
曲线非常相似。事实证明,动态内存分配是整体内存增长的主要因素,因此堆栈变量消耗的内存量很小,似乎是恒定的,可以忽略;- 恒定数量的goroutine保证了没有goroutine泄漏/堆栈变量泄漏;
- 分配的对象总数在进程的生命周期内保持不变(考虑到波动没有意义);
- 最令人惊讶的事实是:
HeapIdle
与Sys
以相同的速度增长,而HeapReleased
始终为零。显然,运行时根本不会将内存返回给操作系统,至少在这个测试条件下是这样的:
> HeapIdle减去HeapReleased估计了可以返回给操作系统的内存量,
> 但由于运行时需要保留以便在不请求更多内存的情况下扩大堆,
> 所以这部分内存被运行时保留。
对于那些试图调查内存消耗问题的人,我建议按照描述的步骤排除一些琐碎的错误(如goroutine泄漏)。
显式释放内存
有趣的是,通过显式调用debug.FreeOSMemory()
可以显著减少内存消耗:
// 在顶级包中
func init() {
go func() {
t := time.Tick(time.Second)
for {
<-t
debug.FreeOSMemory()
}
}()
}
实际上,与默认条件相比,这种方法节省了约35%的内存。
英文:
UPD (2022)
For those who knows Russian, I made a presentation
and wrote couple of articles on this topic:
- RAM consumption in Golang: problems and solutions (Потребление оперативной памяти в языке Go: проблемы и пути решения)
- Preventing Memory Leaks in Go, Part 1. Business Logic Errors (Предотвращаем утечки памяти в Go, ч. 1. Ошибки бизнес-логики)
- Preventing memory leaks in Go, part 2. Runtime features (Предотвращаем утечки памяти в Go, ч. 2. Особенности рантайма)
Original answer (2017)
I was always confused about the growing residential memory of my Go applications, and finally I had to learn the profiling tools that are present in Go ecosystem. Runtime provides many metrics within a runtime.Memstats structure, but it may be hard to understand which of them can help to find out the reasons of memory growth, so some additional tools are needed.
Profiling environment
Use https://github.com/tevjef/go-runtime-metrics in your application. For instance, you can put this in your main
:
import(
metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
//...
metrics.DefaultConfig.CollectionInterval = time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
// handle error
}
}
Run InfluxDB
and Grafana
within Docker
containers:
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0
Set up interaction between Grafana
and InfluxDB
Grafana
(Grafana main page -> Top left corner -> Datasources -> Add new datasource):
Import dashboard #3242 from https://grafana.com (Grafana main page -> Top left corner -> Dashboard -> Import):
Finally, launch your application: it will transmit runtime metrics to the contenerized Influxdb
. Put your application under a reasonable load (in my case it was quite small - 5 RPS for a several hours).
Memory consumption analysis
Sys
(the synonim ofRSS
) curve is quite similar toHeapSys
curve. Turns out that dynamic memory allocation was the main factor of overall memory growth, so the small amount of memory consumed by stack variables seem to be constant and can be ignored;- The constant amount of goroutines garantees the absence of goroutine leaks / stack variables leak;
- The total amount of allocated objects remains the same (there is no point in taking into account the fluctuations) during the lifetime of the process.
- The most surprising fact:
HeapIdle
is growing with the same rate as aSys
, whileHeapReleased
is always zero. Obviously runtime doesn't return memory to OS at all , at least under the conditions of this test:
> HeapIdle minus HeapReleased estimates the amount of memory
> that could be returned to the OS, but is being retained by
> the runtime so it can grow the heap without requesting more
> memory from the OS.
For those who's trying to investigate the problem of memory consumption I would recommend to follow the described steps in order to exclude some trivial errors (like goroutine leak).
Freeing memory explicitly
It's interesting that the one can significantly decrease memory consumption with explicit calls to debug.FreeOSMemory()
:
// in the top-level package
func init() {
go func() {
t := time.Tick(time.Second)
for {
<-t
debug.FreeOSMemory()
}
}()
}
In fact, this approach saved about 35% of memory as compared with default conditions.
答案4
得分: 10
你还可以使用StackImpact,它会自动记录和报告常规和异常触发的内存分配配置文件到仪表板上,这些配置文件以历史和可比较的形式提供。有关更多详细信息,请参阅这篇博文在生产中检测Go应用程序的内存泄漏。
免责声明:我在StackImpact工作。
英文:
You can also use StackImpact, which automatically records and reports regular and anomaly-triggered memory allocation profiles to the dashboard, which are available in a historical and comparable form. See this blog post for more details Memory Leak Detection in Production Go Applications
Disclaimer: I work for StackImpact
答案5
得分: 1
尝试回答以下原始问题:
有没有更好的工具来解释Golang运行时内存?
我发现以下工具很有用:
Statsview
https://github.com/go-echarts/statsview
Statsview集成了标准的net/http/pprof
Statsviz
https://github.com/arl/statsviz
英文:
Attempting to answer the following original question
Is there any better tool to explain golang runtime memory?
I find the following tools useful
Statsview
https://github.com/go-echarts/statsview
Statsview is integrated the standard net/http/pprof
Statsviz
https://github.com/arl/statsviz
答案6
得分: 1
这篇文章对你的问题非常有帮助。
https://medium.com/safetycultureengineering/analyzing-and-improving-memory-usage-in-go-46be8c3be0a8
我运行了一个pprof分析。pprof是Go语言内置的工具,可以对从运行中的应用程序收集的分析和可视化数据进行分析。它是一个非常有帮助的工具,可以从运行中的Go应用程序收集数据,并且是性能分析的一个很好的起点。我建议在生产环境中运行pprof,这样你可以获得客户实际操作的真实样本。
当你运行pprof时,你会得到一些文件,这些文件关注goroutines、CPU、内存使用情况和其他一些根据你的配置而定的内容。我们将专注于堆文件,以深入了解内存和GC统计信息。我喜欢在浏览器中查看pprof,因为我发现这样更容易找到可操作的数据点。你可以使用以下命令来实现。
go tool pprof -http=:8080 profile_name-heap.pb.gz
pprof也有一个命令行工具,但我更喜欢浏览器选项,因为我觉得它更容易导航。我个人推荐使用火焰图。我发现它是最容易理解的可视化工具,所以我大部分时间都使用这个视图。火焰图是函数调用栈的可视化版本。顶部的函数是被调用的函数,下面的所有函数都是在执行该函数时调用的。你可以点击单个函数调用来放大它们,从而改变视图。这样可以更深入地了解特定函数的执行情况,非常有帮助。请注意,火焰图显示消耗最多资源的函数,因此某些函数可能不会显示在其中。这样可以更容易地找出最大的瓶颈所在。
这对你有帮助吗?
英文:
This article will be pretty much helpful for your problem.
https://medium.com/safetycultureengineering/analyzing-and-improving-memory-usage-in-go-46be8c3be0a8
I ran a pprof analysis. pprof is a tool that’s baked into the Go language that allows for analysis and visualisation of profiling data collected from a running application. It’s a very helpful tool that collects data from a running Go application and is a great starting point for performance analysis. I’d recommend running pprof in production so you get a realistic sample of what your customers are doing.
When you run pprof you’ll get some files that focus on goroutines, CPU, memory usage and some other things according to your configuration. We’re going to focus on the heap file to dig into memory and GC stats. I like to view pprof in the browser because I find it easier to find actionable data points. You can do that with the below command.
go tool pprof -http=:8080 profile_name-heap.pb.gz
pprof has a CLI tool as well, but I prefer the browser option because I find it easier to navigate. My personal recommendation is to use the flame graph. I find that it’s the easiest visualiser to make sense of, so I use that view most of the time. The flame graph is a visual version of a function’s stack trace. The function at the top is the called function, and everything underneath it is called during the execution of that function. You can click on individual function calls to zoom in on them which changes the view. This lets you dig deeper into the execution of a specific function, which is really helpful. Note that the flame graph shows the functions that consume the most resources so some functions won’t be there. This makes it easier to figure out where the biggest bottlenecks are.
Is this helpful?
答案7
得分: 1
尝试使用Tracy的GO插件。Tracy是一个实时、纳秒级分辨率的远程遥测工具。GoTracy是与Tracy连接并发送必要信息以更好地理解应用程序进程的代理。导入插件后,您可以像下面的描述中那样放置遥测代码:
func exampleFunction() {
gotracy.TracyInit()
gotracy.TracySetThreadName("exampleFunction")
for i := 0.0; i < math.Pi; i += 0.1 {
zoneid := gotracy.TracyZoneBegin("Calculating Sin(x) Zone", 0xF0F0F0)
gotracy.TracyFrameMarkStart("Calculating sin(x)")
sin := math.Sin(i)
gotracy.TracyFrameMarkEnd("Calculating sin(x)")
gotracy.TracyMessageLC("Sin(x) = "+strconv.FormatFloat(sin, 'E', -1, 64), 0xFF0F0F)
gotracy.TracyPlotDouble("sin(x)", sin)
gotracy.TracyZoneEnd(zoneid)
gotracy.TracyFrameMark()
}
}
插件位于:
https://github.com/grzesl/gotracy
Tracy位于:
https://github.com/wolfpld/tracy
英文:
Try GO plugin for Tracy. Tracy is "A real time, nanosecond resolution, remote telemetry" (...).
GoTracy (name of the plugin) is the agent with connect with the Tracy and send necessary information to better understand your app process. After importing plugin You can put telemetry code like in description below:
func exampleFunction() {
gotracy.TracyInit()
gotracy.TracySetThreadName("exampleFunction")
for i := 0.0; i < math.Pi; i += 0.1 {
zoneid := gotracy.TracyZoneBegin("Calculating Sin(x) Zone", 0xF0F0F0)
gotracy.TracyFrameMarkStart("Calculating sin(x)")
sin := math.Sin(i)
gotracy.TracyFrameMarkEnd("Calculating sin(x)")
gotracy.TracyMessageLC("Sin(x) = "+strconv.FormatFloat(sin, 'E', -1, 64), 0xFF0F0F)
gotracy.TracyPlotDouble("sin(x)", sin)
gotracy.TracyZoneEnd(zoneid)
gotracy.TracyFrameMark()
}
}
The plugin is placed in:
https://github.com/grzesl/gotracy
The Tracy is placed in:
https://github.com/wolfpld/tracy
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论