pprof 和 ps 之间的内存使用差异

2024-05-25

我一直在尝试分析用 cobra 构建的 cli 工具的堆使用情况。 这pprof工具显示如下,

Flat    Flat%   Sum%    Cum Cum%    Name    Inlined?
1.58GB  49.98%  49.98%  1.58GB  49.98%  os.ReadFile 
1.58GB  49.98%  99.95%  1.58GB  50.02%  github.com/bytedance/sonic.(*frozenConfig).Unmarshal    
0       0.00%   99.95%  3.16GB  100.00% runtime.main    
0       0.00%   99.95%  3.16GB  100.00% main.main   
0       0.00%   99.95%  3.16GB  100.00% github.com/spf13/cobra.(*Command).execute   
0       0.00%   99.95%  3.16GB  100.00% github.com/spf13/cobra.(*Command).ExecuteC  
0       0.00%   99.95%  3.16GB  100.00% github.com/spf13/cobra.(*Command).Execute   (inline)
0       0.00%   99.95%  3.16GB  100.00% github.com/mirantis/broker/misc.ParseUcpNodesInspect    
0       0.00%   99.95%  3.16GB  100.00% github.com/mirantis/broker/cmd.glob..func3  
0       0.00%   99.95%  3.16GB  100.00% github.com/mirantis/broker/cmd.getInfos 
0       0.00%   99.95%  3.16GB  100.00% github.com/mirantis/broker/cmd.Execute  
0       0.00%   99.95%  1.58GB  50.02%  github.com/bytedance/sonic.Unmarshal

But ps正在播种,几乎消耗殆尽6752.23 Mb(rss).

另外,我把defer profile.Start(profile.MemProfileHeap).Stop()最后一个函数被执行。将分析器放入func main没有显示任何内容。于是我跟踪了一下函数,发现最后一个函数占用了相当大的内存。

我的问题是,如何找到丢失的 ~3GB 内存?


有多个问题(与您的问题有关):

  1. ps (and top等)显示多个内存读数。唯一感兴趣的通常称为RES or RSS https://en.wikipedia.org/wiki/Resident_set_size。你不知道那是哪一个。
    基本上,看看通常命名的阅读材料VIRT没意思。

  2. 正如沃尔克所说,pprof不测量内存消耗,它测量(以您运行它的模式)内存分配率,即“多少”,而不是“频率”。

    要理解它的含义,请考虑如何pprof作品。 在分析过程中,计时器会滴答作响,每次滴答时,分析器都会对正在运行的程序进行快照,扫描所有活动 goroutine 的堆栈,并将堆上的活动对象属性赋予这些堆栈的堆栈帧中包含的变量,以及每个堆栈框架属于活动功能。

    这意味着,如果您的进程将调用,例如,os.ReadFile—根据其约定,分配足够长的字节片以包含要读取的文件的全部内容,—每次读取 1 GiB 文件 100 次,分析器的计时器将设法精确定位这 100 次调用中的每一次(它在采样时可能会错过一些调用),os.ReadFile将归因于已分配 100 GiB。
    But如果您的程序的编写方式不是保存这些调用返回的每个切片,而是对这些切片执行某些操作并在处理后将它们丢弃,则过去调用的切片可能已经被 GC 收集到分配新的时。

  3. 虽然不要求the spec https://golang.org/ref/spec,Go 的两个“标准”当代实现——最初被称为“gc”的一个,大多数人认为它是the实现,以及 GCC 前端 - 具有与您自己的进程流并行运行的垃圾收集器;它实际收集您的进程产生的垃圾的那一刻是由一组复杂的启发式控制的(开始here https://tip.golang.org/doc/gc-guide如果感兴趣的话)它会尝试在 GC 花费 CPU 时间和不执行 GC 花费 RAM 之间取得平衡;-),这意味着对于短期进程,GC 可能不会启动一次,这意味着您的进程将以所有生成的垃圾仍然浮动,并且当进程结束时,所有内存都将由操作系统以通常的方式回收。

  4. 当GC收集垃圾时,释放的内存并不会立即返回给操作系统。相反,涉及两阶段过程:

    • 首先,释放的区域返回到内存管理器,这是为正在运行的程序提供支持的 Go rutime 的一部分。 这是一件明智的事情,因为在典型的程序中,内存变动通常足够高,并且释放的内存可能会很快再次分配回来。

    • 其次,内存页保持足够长的空闲时间是marked https://manpages.debian.org/2/madvise让操作系统知道它可以使用它来满足自己的需要。

    基本上,这意味着即使 GC 释放了一些内存,您也不会在正在运行的 Go 进程之外看到它,因为这些内存首先会重新调整到进程自己的池中。

  5. 不同版本的 Go(同样,我的意思是“gc”实现)实施了关于将释放的页面返回到操作系统的不同策略:首先,它们被标记为madvise(2) https://manpages.debian.org/2/madvise as MADV_FREE,那么作为MADV_DONTNEED然后再次作为MADV_FREE。 如果您碰巧使用运行时将释放的内存标记为的 Go 版本MADV_DONTNEED,读数RSS https://en.wikipedia.org/wiki/Resident_set_size会更不明智,因为以这种方式标记的内存仍然对进程不利'RSS https://en.wikipedia.org/wiki/Resident_set_size尽管操作系统被暗示它可以在需要时回收该内存。

回顾一下。 这个话题足够复杂,你似乎太快得出某些结论了;-)


更新。我决定稍微扩展一下内存管理,因为我觉得你头脑中的这些东西的大局中可能缺少某些点点滴滴,正因为如此,你可能会发现对你的问题的评论是毫无意义和不屑一顾的。

建议不要测量用 Go 编写的程序的内存消耗的原因ps, top和朋友们的根源在于内存管理的实现运行时环境 https://en.wikipedia.org/wiki/Runtime_system用当代高级编程语言编写的程序与操作系统内核及其运行的硬件中实现的底层内存管理相去甚远。

让我们考虑一下 Linux 有具体的例子。
你当然可以直接要求内核为你分配一块内存:mmap(2) https://manpages.debian.org/2/mmap is a syscall https://en.wikipedia.org/wiki/System_call这样做的。 如果你用MAP_PRIVATE(通常也与MAP_ANONYMOUS),内核将确保进程的页表有一个或多个新条目pages https://en.wikipedia.org/wiki/Page_(computer_memory)内存以包含您所请求的字节数的连续区域,并返回序列中第一页的地址。
这时候你可能会认为RSS https://en.wikipedia.org/wiki/Resident_set_size您的进程已增长了该字节数,但事实并非如此:内存是“保留”的,但实际上并未分配;为了真正分配内存页面,进程必须“接触”页面内的任何字节 - 通过读取或写入它:这将在 CPU 和内核处理程序上生成所谓的“页面错误”会要求硬件实际分配一个真正的“硬件”内存页。只有在那之后,该页面才会真正计入该进程'RSS https://en.wikipedia.org/wiki/Resident_set_size.

好吧,这很有趣,但您可能会看到一个问题:使用完整的页面进行操作不太方便(在不同的系统上,页面的大小可能不同;通常在 x86 谱系的系统上为 4 KiB):当您在高级语言,你不会在这么低的层面上思考记忆;相反,您期望正在运行的程序以某种方式实现您需要的“对象”(我在这里并不是指 OOP;只是包含某些语言或用户定义类型的值的内存片段)。 这些对象可以是任何大小,大多数时候比单个内存页小,而且更重要的是,大多数时候您甚至不会考虑这些对象在分配时消耗了多少空间。 即使使用 C 语言(如今被认为是相当低级的语言)进行编程,您通常也习惯于在 C 语言中使用内存管理函数。malloc(3) https://manpages.debian.org/3/malloc系列由标准 C 库提供,允许您分配任意大小的内存区域。

解决此类问题的一种方法是使用更高级别的内存管理器on top内核可以为你的程序做什么,事实上,每一个用高级语言(甚至 C 和 C++!)编写的通用程序都在使用一种: 对于解释性语言(例如 Perl、Tcl、Python 、POSIX shell 等)由解释器提供;对于字节编译语言(例如 Java),它由执行该代码的进程提供(例如 Java 的 JRE);对于编译为机器 (CPU) 代码的语言(例如 Go 的“库存”实现),它由包含在生成的可执行映像文件中的“运行时”代码提供,或者在将其加载到程序中时动态链接到程序中。用于执行的内存。
此类内存管理器通常非常复杂,因为它们必须处理许多复杂的问题,例如内存碎片,并且它们通常必须尽可能避免与内核对话,因为系统调用很慢。
后一个要求自然意味着进程级内存管理器尝试缓存它们曾经从内核获取的内存,并且不愿意将其释放回来。

所有这些意味着,比如说,在一个典型的active去编程你可能会疯狂记忆搅动— 成群的小对象一直被分配和释放,这对值几乎没有影响RSS https://en.wikipedia.org/wiki/Resident_set_size从进程的“外部”进行监控:所有这些扰动都由进程内内存管理器和(如普通 Go 实现的情况)处理,GC 自然与 MM 紧密集成。

因此,为了对长期运行的生产级 Go 程序中发生的情况有有用的、可操作的想法,此类程序通常提供一组不断更新的metrics(传送、收集和监控它们称为遥测)。对于 Go 程序,负责生成这些指标的程序的一部分可以定期调用runtime.ReadMemStats https://pkg.go.dev/runtime#ReadMemStats and runtime/debug.ReadGCStats https://pkg.go.dev/runtime/debug#ReadGCStats或者直接使用什么runtime/metrics https://pkg.go.dev/runtime/metrics必须提供。在 Zabbix、Graphana 等监控系统中查看此类指标非常有启发性:您可以从字面上看到进程内 MM 可用的空闲内存量在每个 GC 周期后如何增加,而RSS https://en.wikipedia.org/wiki/Resident_set_size保持大致相同。

另请注意,您可能会考虑在特殊环境变量中使用各种与 GC 相关的调试设置来运行 Go 程序GODEBUG描述here https://pkg.go.dev/runtime#hdr-Environment_Variables:基本上,你让 Go 运行时为你正在运行的程序提供动力,发出有关 GC 如何工作的详细信息(另请参阅this https://dave.cheney.net/2014/07/11/visualising-the-go-garbage-collector).

希望这会让您好奇并进一步探索这些问题;-)

你可能会发现this https://povilasv.me/go-memory-management/很好地介绍了 Go 运行时实现的内存管理——与内核和硬件相关;推荐阅读。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

pprof 和 ps 之间的内存使用差异 的相关文章

  • 为什么 OS X 和 Linux 之间的 UTF-8 文本排序顺序不同?

    我有一个包含 UTF 8 编码文本行的文本文件 mac os x cat unsorted txt foo foo 津 如果它有助于重现问题 这里是文件中确切字节的校验和和转储 以及如何自己生成文件 在 Linux 上 使用base64 d
  • Python glob,操作系统,相对路径,将文件名放入列表中[重复]

    这个问题在这里已经有答案了 我正在尝试创建一个目录中所有文件的列表 其中文件名以 root 结尾 在阅读了论坛中的一些文章后 我尝试使用 glob 和 os listdir 的基本策略 但我都遇到了麻烦 首先 当我使用 import glo
  • Ruby:在 Ubuntu 上安装 rmagick

    我正在尝试在 Ubuntu 10 04 上安装 RMagick 看起来here https stackoverflow com questions 1482823 is there an easy way to install rmagic
  • Linux shell 从用户输入中获取设备 ID

    我正在为一个程序编写安装脚本 该程序需要在其配置中使用 lsusb 的设备 ID 因此我正在考虑执行以下操作 usblist lsusb put the list into a array for each line use the arr
  • BASH:输入期间按 Ctrl+C 会中断当前终端

    我的 Bash 版本是 GNU bash version 4 3 11 1 release x86 64 pc linux gnu 我有一段这样的代码 while true do echo n Set password read s pas
  • 在处理程序之后访问 HTTP 请求上下文

    在我的日志记录中间件 链中的第一个 中 我需要访问一些在链下游的某些身份验证中间件中编写的上下文 并且仅在处理程序本身执行之后 旁注 需要首先调用日志记录中间件 因为我需要记录请求的持续时间 包括在中间件中花费的时间 此外 当权限不足时 身
  • /sys/device/ 和 dmidecode 报告的不同 CPU 缓存大小

    我正在尝试获取系统中不同缓存级别的大小 我尝试了两种技术 a 使用 sys device 中的信息 这是输出 cat sys devices system cpu cpu0 cache index1 size 32K cat sys dev
  • 为 Linux 编译 Objective-C 应用程序(API 覆盖范围)

    我可能在这里问一些奇怪的问题 但我不确定从哪里开始 问题是我正在考虑使用 Obj C 和 Foundation 类在 Mac 上编写一个命令行工具 但存在一个非常大的风险 那就是我希望能够为不同的 Linux 发行版编译它 以便将来作为服务
  • Linux无法删除文件

    当我找到文件时 我在删除它们时遇到问题 任务 必须找到带有空格的文件并将其删除 我的尝试 rm find L root grep i 但我有错误 rm cannot remove root test No such file or dire
  • 如何在线程创建和退出时调用函数?

    include
  • 如何在 Linux 上通过 FTP 递归下载文件夹 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 如何才能将 TCP 连接返回到同一端口?

    机器是 RHEL 5 3 内核 2 6 18 有时我在 netstat 中注意到我的应用程序有连接 建立了 TCP 连接本地地址 and 国外地址是一样的 其他人也报告了同样的问题 症状与链接中描述的相同 客户端连接到本地运行的服务器的端口
  • 在 docker 中重定向命令输出

    我想为我的服务器做一些简单的日志记录 它是一个在 Docker 容器中运行的小型 Flask 应用程序 这是 Dockerfile Dockerfile FROM dreen flask MAINTAINER dreen WORKDIR s
  • ioctl 命令的用户权限检查

    我正在实现 char 驱动程序 Linux 并且我的驱动程序中有某些 IOCTL 命令仅需要由 ADMIN 执行 我的问题是如何在 ioctl 命令实现下检查用户权限并限制非特权用户访问 IOCTL 您可以使用bool capable in
  • 批量删除文件名中包含 BASH 中特殊字符的子字符串

    我的目录中有一个文件列表 opencv calib3d so2410 so opencv contrib so2410 so opencv core so2410 so opencv features2d so2410 so opencv
  • 找不到包“gdk-pixbuf-2.0”

    我正在尝试在 Amazon Linux 发行版实例上构建 librsvg 我已经通过 yum 安装了大部分依赖项 其中一些在实例上启用的默认 yum 存储库中不可用 因此必须从头开始构建它们 我已经走了很远 但还停留在最后一点 跑步时sud
  • (转)如何使用toml文件?

    正如标题 我想知道如何使用 golang 中的 toml 文件 在此之前 我展示了我的 toml 示例 这样对吗 datatitle enable true userids 12345 67890 datatitle 12345 prop1
  • ALSA:snd_pcm_writei 调用时缓冲区不足

    当运行我最近从灰烬中带回来的旧程序时 我遇到了缓冲区不足的情况 该程序将原始声音文件完全加载到内存中 2100 字节长 525 帧 并准备 ALSA 进行输出 44 1khz 2 通道 有符号 16 位 if err snd pcm set
  • 适用于 Linux 的轻量级 IDE [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 使用 \r 并打印一些文本后如何清除控制台中的一行?

    对于我当前的项目 有一些代码很慢并且我无法使其更快 为了获得一些关于已完成 必须完成多少的反馈 我创建了一个进度片段 您可以在下面看到 当你看到最后一行时 sys stdout write r100 80 n I use 80覆盖最终剩余的

随机推荐