我有一个在 haskell 中实现的服务器进程,它充当一个简单的内存数据库。客户端进程可以连接然后添加和检索数据。该服务使用的内存比我预期的要多,我正在尝试找出原因。
我拥有的最粗略的衡量标准是 linux “top”。当我开始这个过程时,我看到一个“VIRT”图像大小约为 27MB。运行客户端插入 60,000 个数据项后,我看到图像大小约为 124MB。
运行捕获 GC 统计信息的过程(+RTS -S),我最初看到
Alloc Copied Live GC GC TOT TOT Page Flts
bytes bytes bytes user elap user elap
28296 8388 9172 0.00 0.00 0.00 0.32 0 0 (Gen: 1)
在添加 60k 项时,我看到实时字节平稳增长到
...
532940 14964 63672180 0.00 0.00 23.50 31.95 0 0 (Gen: 0)
532316 7704 63668672 0.00 0.00 23.50 31.95 0 0 (Gen: 0)
530512 9648 63677028 0.00 0.00 23.50 31.95 0 0 (Gen: 0)
531936 10796 63686488 0.00 0.00 23.51 31.96 0 0 (Gen: 0)
423260 10047016 63680532 0.03 0.03 23.53 31.99 0 0 (Gen: 1)
531864 6996 63693396 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531852 9160 63703536 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531888 9572 63711876 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531928 9716 63720128 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531856 9640 63728052 0.00 0.00 23.55 32.02 0 0 (Gen: 0)
529632 9280 63735824 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
527948 8304 63742524 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
528248 7152 63749180 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
528240 6384 63756176 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
341100 10050336 63731152 0.03 0.03 23.58 32.35 0 0 (Gen: 1)
5080 10049728 63705868 0.03 0.03 23.61 32.70 0 0 (Gen: 1)
这似乎告诉我堆有大约 63MB 的实时数据。当您添加堆栈空间、代码空间、GC 开销等时,这很可能与顶部的数字一致。
所以我尝试使用堆分析器来确定是什么组成的
这 63MB。结果令人困惑。运行“+RTS -h”,查看
生成的 hp 文件,最后一个也是最大的快照有:
containers-0.3.0.0:Data.Map.Bin 1820400
bytestring-0.9.1.7:Data.ByteString.Internal.PS 1336160
main:KV.Store.Memory.KeyTree 831972
main:KV.Types.KF_1 750328
base:GHC.ForeignPtr.PlainPtr 534464
base:Data.Maybe.Just 494832
THUNK 587140
快照中的所有其他数字都比这小得多。
将这些相加得出的峰值内存使用量约为 6MB,如
图表输出:
为什么这与 GC 统计信息中显示的事件字节不一致?它是
很难看出我的数据结构可能需要 63MB,而且
探查器说他们不是。内存去哪儿了?
感谢您对此的任何提示或指示。
蒂姆
最佳答案
我有一个理论。我的理论是你的程序使用了很多类似 ByteStrings
的东西。 .我的理论是因为ByteStrings
的主要内容是 malloc
ated,它们在分析时不会显示。因此,如果堆的最大内容没有显示在分析图上,您可能会用完堆。
更糟糕的是,当您获取 ByteStrings
的子字符串时,它们默认保留指向最初分配的内存块的指针。因此,即使您尝试只存储一些 ByteString
的小片段您最终可能会保留整个最初分配的 ByteString
这不会显示在您的堆配置文件中。
无论如何,这是我的理论。我对 GHC 的堆分析器如何工作以及 ByteStrings
的工作原理知之甚少。实现是为了确定。也许其他人可以插话并证实或质疑我的理论。
Edit2: tibbe 指出 ByteString
使用的缓冲区s 被固定。因此,如果您要分配/释放大量小 Bytestring
s,你可以分割你的堆,这意味着你用完了可用的堆,其中一半未分配。
编辑:JaffaCake 告诉我,有时堆分析器不会显示 ByteStrings 分配的内存。
关于haskell - 我应该如何解释 ghc 堆分析器的输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5306717/