如何找到扭曲服务器内存使用增加的来源?
我有一个用Python写的音频广播服务器,基于Twisted框架。它运行得很好,但当服务器上的用户增多时,内存使用量也在增加。而当这些用户下线后,内存使用量却从来没有下降。你可以在下面的图中看到:
从图中可以看出,当听众/广播的数量上升时,内存使用量的曲线也随之上升。但在听众/广播数量达到峰值后,内存使用量依然很高,始终没有下降。
我尝试了以下方法来解决这个问题:
- 将Twisted从8.2升级到9.0
- 使用guppy来查看内存使用情况,但没有任何帮助
- 将选择器反应器切换到epoll反应器,问题依旧。
- 使用objgraph绘制对象关系图,但我看不出什么有用的信息。
这是我运行Twisted服务器的环境:
- Python: 2.5.4 r254:67916
- 操作系统: Linux版本 2.6.18-164.9.1.el5PAE (mockbuild@builder16.centos.org) (gcc版本 4.1.2 20080704 (Red Hat 4.1.2-46))
- Twisted: 9.0 (在virtualenv下)
以下是guppy的内存转储:
Partition of a set of 116280 objects. Total size = 9552004 bytes.
Index Count % Size % Cumulative % Type
0 52874 45 4505404 47 4505404 47 str
1 5927 5 2231096 23 6736500 71 dict
2 29215 25 1099676 12 7836176 82 tuple
3 7503 6 510204 5 8346380 87 types.CodeType
4 7625 7 427000 4 8773380 92 function
5 672 1 292968 3 9066348 95 type
6 866 1 82176 1 9148524 96 list
7 1796 2 71840 1 9220364 97 __builtin__.weakref
8 1140 1 41040 0 9261404 97 __builtin__.wrapper_descriptor
9 2603 2 31236 0 9292640 97 int
从中可以看到,总大小为9552004字节,也就是9.1 MB,你可以看到通过ps命令报告的rss:
[xxxx@webxx ~]$ ps -u xxxx-o pid,rss,cmd
PID RSS CMD
22123 67492 twistd -y broadcast.tac -r epoll
我服务器的rss是65.9 MB,这意味着在我的服务器上有56.8 MB的不可见内存使用,这些是什么呢?
我有以下几个问题:
- 如何找到内存使用量增加的来源?
- guppy能看到的内存使用量是什么?
- 那些不可见的内存使用量是什么?
- 这是否是某些用C语言编写的模块造成的内存泄漏?如果是的话,我该如何追踪和修复?
- Python是如何管理内存的?是内存池吗?我觉得这可能是音频数据块造成的。所以在Python解释器拥有的内存块中可能有小的泄漏。
更新 2010/1/20: 有趣的是,我下载了最新的日志文件,显示内存在某个时刻并没有增加。我想可能是分配的内存空间足够大。以下是最新的图。
更新 2010/1/21: 这里还有另一张图。嗯……稍微有一点上升。
哎呀……还是在上升。
3 个回答
你有没有考虑过使用CentOS的一个替代工具,叫做SystemTap?
这个工具可以让你比较深入地了解你系统里发生了什么,尤其是在你的*nix进程内部……虽然有点不确定,但可能会让你更清楚进程之间的活动。
这个问题很有意思。期待看到其他人的回复。
Ben
听起来像是C语言模块中的内存泄漏。Valgrind是一个很好的工具,可以帮助你找出和内存分配相关的问题。不过,我不太确定它在处理运行时加载的模块时效果如何……
根据我的猜测,这个问题是由于内存碎片化造成的。最初的设计是把音频数据分成一块一块的存放在一个列表里,这些块的大小都是不固定的。当这个列表的总大小超过了缓冲区的限制时,就会从列表的顶部弹出一些块,以控制大小。它可能看起来像这样:
- 块大小 511
- 块大小 1040
- 块大小 386
- 块大小 1350
- ...
大部分块的大小都超过了256字节,Python在处理超过256字节的块时,会使用malloc而不是内存池。你可以想象这些块是如何分配和释放的,那会发生什么呢?比如,当1350字节的块被释放时,堆中可能会留下1350字节的空闲空间。接着,如果又来了一个请求需要988字节,malloc就会利用这个空洞,然后又会留下一个362字节的小空洞。随着时间的推移,堆中会出现越来越多的小空洞,换句话说,堆中会有很多碎片。虚拟内存的页面大小通常是4KB,这些碎片分布在堆的一个大范围内,这让操作系统无法将这些页面交换出去。因此,RSS(常驻集大小)总是很高。
在我对服务器的音频块管理模块进行修改后,现在使用的内存变得很少。你可以看看这个图,并与之前的图进行比较。
新的设计使用了bytearray而不是字符串列表。这是一块大的内存,所以不再有碎片问题。