python - 了解 Python 进程的内存增长(VmRSS 与 gc.get_objects)

标签 python linux memory memory-leaks garbage-collection

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

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 代码中查找内存泄漏的问题,但这不是这个问题的主题。相反,我试图了解我的代码随着时间的推移的内存消耗以及它是否可能遭受内存泄漏的影响。
特别是,我试图了解两个不同的内存使用指标的差异:
  • 已分配对象的数量 (reported by gc.get_objects ) 和
  • 实际使用的物理内存量 (read from VmRSS on a Linux system)。

  • 为了研究这两个指标,我将上面的原始代码扩展如下:
    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 之前大致相同之后 , 对?事实上,这就是我观察到的分配对象的数量。然而,内存的消耗却在稳步增长:
    indicators over time
    现在我的核心问题:这些观察结果是否表明内存泄漏?据我了解,Python 中的内存泄漏将通过分配对象的增长来表示,我们在这里没有观察到。另一方面,为什么内存消耗会稳定增长?
    为了进一步调查,我还进行了第二次测试。对于这个测试,我反复调用 process_input(i)使用固定输入 i (每次五次)并记录两次迭代之间的内存消耗:
  • 对于 i=12 ,内存消耗保持在 10.91 GiB 不变。
  • 对于 i=14 ,内存消耗保持在 7.00 GiB 不变。

  • 我认为,这些观察结果更不可能出现内存泄漏,对吧?但是,为什么内存消耗没有落在迭代之间的一个可能的解释 ,鉴于 process_input是无状态的吗?
    该系统共有 32 GiB RAM,运行 Ubuntu 20.04。 Python 版本是 3.6.10。 process_input函数使用了几个第三方库。

    最佳答案

    一般来说,RSS 并不是一个特别好的指标,因为它是“常驻”的集合大小,即使是一个相当笨拙的进程,就提交的内存而言,也可以有一个适度的 RSS,因为内存可以被换出。您可以查看/proc/self/smaps 并将可写区域的大小相加以获得更好的基准。
    另一方面,如果确实存在增长,并且您想了解原因,则需要查看实际动态分配的内存。我建议使用 https://github.com/vmware/chap
    为此,只需将 1 秒的 sleep 时间延长一点,在调用 sleep 之前进行打印,然后使用来自另一个 session 的 gcore 在其中一些 sleep 期间收集事件核心。
    因此,假设您从输入为 14 到 21 时收集了核心。使用 chap 查看每个核心,例如,使用以下命令:

    count used
    
    这将使您对已请求但未释放的分配有一个很好的了解。如果后期核心的数字要大得多,您可能会遇到某种增长问题。如果这些数字确实相差很大,请使用
    summarize used
    
    如果您有增长,则可能存在泄漏(与某些容器简单地膨胀相反)。要检查这一点,您可以尝试以下命令
    count leaked
    show leaked 
    
    从那里您可能应该查看文档,具体取决于您找到的内容。
    OTOH,如果使用的分配不是问题,则可以尝试以下操作,以查看已释放但属于较大内存区域的分配的内存,因为这些区域的某些部分仍在使用中,因此无法返回给操作系统:
    count free
    summarize free
    
    如果“使用”分配或“免费”分配都不是问题,您可以尝试:
    summarize writable
    
    这是所有可写内存的一个非常高级的 View 。例如,您可以看到堆栈使用情况...

    关于python - 了解 Python 进程的内存增长(VmRSS 与 gc.get_objects),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62852644/

    相关文章:

    c++ - Qt Creator 在 linux mint 64 位中找不到 CMAKE_CXX_COMPILER 编译器

    c++ - Qt - 如何处理对话框的内存管理?

    memory - 四端口内存来自单端口内存还是双端口内存?

    memory - 现代操作系统是否使用分页和分段?

    python - 将英特尔的 __attribute__((vector)) 与 swig 一起使用

    python - 将字符串列表传递给结构任务

    python - 切片和椭圆索引操作会产生什么结果?

    python - Imagekit - 删除原始图像后未删除缓存图像

    linux - Linux 上的 Python3 交互模式两次启动代码

    linux - 如何在 x86 linux 上执行 MIPS 汇编程序?