首页 > 解决方案 > 了解 Python 进程的内存增长(VmRSS 与 gc.get_objects)

问题描述

在不涉及算法细节的情况下,假设我的代码按顺序处理输入列表:

inputs = [2,5,6,7,8,10,12,13,14,15,16,17,18,19,20,21]
for i in inputs:
    process_input(i)

为简单起见,让我们考虑process_input一个无状态的黑盒。

我知道这个站点充满了关于在 Python 代码中查找内存泄漏的问题,但这不是这个问题的主题。相反,我试图了解我的代码随着时间的推移的内存消耗以及它是否可能遭受内存泄漏的影响。

特别是,我试图了解两个不同的内存使用指标的差异:

为了研究这两个指标,我将上面的原始代码扩展如下:

import time, gc

def get_current_memory_usage():
    with open('/proc/self/status') as f:
        memusage = f.read().split('VmRSS:')[1].split('\n')[0][:-3]
    return int(memusage.strip()) / (1024 ** 2)

inputs = [2,5,6,7,8,10,12,13,14,15,16,17,18,19,20,21]
gc.collect()

last_object_count = len(gc.get_objects())

for i in inputs:

    print(f'\nProcessing input {i}...')
    process_input(i)

    gc.collect()
    time.sleep(1)
    memory_usage = get_current_memory_usage()
    object_count = len(gc.get_objects())
    print(f'Memory usage: {memory_usage:.2f} GiB')
    print(f'Object count: {object_count - last_object_count:+}')
    last_object_count = object_count

请注意,它process_input是无状态的,即输入的顺序无关紧要。因此,我们希望两个指标在运行之前和之后大致相同process_input,对吧?事实上,这就是我观察到的分配对象的数量。然而,内存的消耗却在稳步增长:

时间指标

现在我的核心问题是: 这些观察是否表明内存泄漏?据我了解,Python 中的内存泄漏将通过分配对象的增长来表示,我们在这里没有观察到。另一方面,为什么内存消耗会稳定增长?

为了进一步调查,我还进行了第二次测试。process_input(i)对于这个测试,我使用一个固定的输入i (每次 5 次)反复调用,并记录了两次迭代之间的内存消耗:

我认为,这些观察结果更不可能出现内存泄漏,对吧?但是,鉴于这是无状态的,为什么内存消耗没有落在迭代之间的可能解释是什么?process_input

该系统共有 32 GiB RAM,运行 Ubuntu 20.04。Python 版本是 3.6.10。该process_input函数使用了几个第三方库。

标签: pythonlinuxmemorymemory-leaksgarbage-collection

解决方案


一般来说,RSS 并不是一个特别好的指标,因为它是“常驻”的集合大小,即使是一个相当笨拙的进程,就提交的内存而言,也可以有一个适度的 RSS,因为内存可以被换出。您可以查看 /proc/self/smaps 并将可写区域的大小相加以获得更好的基准。

另一方面,如果确实存在增长,并且您想了解原因,则需要查看实际动态分配的内存。我建议使用https://github.com/vmware/chap

为此,只需将那 1 秒的睡眠时间延长一点,在调用 sleep 之前进行打印,然后使用另一个会话中的 gcore 在其中一些睡眠期间收集一个实时核心。

因此,假设您从输入为 14 到 21 时收集了核心。使用 chap 查看每个核心,例如,使用以下命令:

count used

这将使您对已请求但未释放的分配有一个很好的了解。如果后期核心的数字要大得多,您可能会遇到某种增长问题。如果这些数字确实相差很大,请使用

summarize used

如果您有增长,则可能存在泄漏(与某些容器简单地膨胀相反)。要检查这一点,您可以尝试以下命令

count leaked
show leaked 

从那里您可能应该查看文档,具体取决于您找到的内容。

OTOH,如果使用的分配不是问题,则可以尝试以下操作,以查看已释放但属于较大内存区域的分配的内存,因为这些区域的某些部分仍在使用中,因此无法返回给操作系统:

count free
summarize free

如果“使用”分配或“免费”分配都不是问题,您可以尝试:

summarize writable

这是所有可写内存的一个非常高级的视图。例如,您可以看到堆栈使用情况...


推荐阅读