Python - 解决内存泄漏问题

时间:2009-10-29 01:58:58

标签: python memory-leaks

我有一个Python程序,它运行一系列实验,没有数据打算从一个测试存储到另一个测试。我的代码包含一个我完全无法找到的内存泄漏(我看看内存泄漏的other threads)。由于时间限制,我不得不放弃寻找泄漏,但如果我能够隔离每个实验,程序可能会运行足够长的时间来产生我需要的结果。

  • 在单独的线程中运行每个测试会有帮助吗?
  • 是否有其他方法可以隔离泄漏的影响?

具体情况详情

  • 我的代码包含两部分:实验运行器和实际实验代码。
  • 虽然在运行所有实验的代码和每个实验使用的代码之间没有共享全局变量,但是必须共享某些类/函数。
  • 实验运行器不仅仅是一个简单的for循环,可以很容易地放入shell脚本中。它首先决定在给定配置参数的情况下需要运行的测试,然后运行测试然后以特定方式输出数据。
  • 我尝试手动调用垃圾收集器,以防问题只是垃圾收集没有运行,但这不起作用

更新

Gnibbler的答案实际上让我发现我的ClosenessCalculation对象存储了所有在每次计算中使用的数据都没有被终止。然后我用它来手动删除一些似乎已修复内存问题的链接。

4 个答案:

答案 0 :(得分:57)

你可以使用这样的东西来帮助追踪内存泄漏

>>> from collections import defaultdict
>>> from gc import get_objects
>>> before = defaultdict(int)
>>> after = defaultdict(int)
>>> for i in get_objects():
...     before[type(i)] += 1 
... 

现在假设测试泄漏了一些内存

>>> leaked_things = [[x] for x in range(10)]
>>> for i in get_objects():
...     after[type(i)] += 1
... 
>>> print [(k, after[k] - before[k]) for k in after if after[k] - before[k]]
[(<type 'list'>, 11)]

11因为我们泄漏了一个包含10个以上列表的列表

答案 1 :(得分:4)

线程无济于事。如果您必须放弃查找泄漏,那么包含其效果的唯一解决方案是偶尔运行一个新的进程(例如,当测试使总体内存消耗过高时,您的喜好 - 您可以通过在Linux中读取/proc/self/status以及在其他操作系统上读取其他类似方法来轻松确定VM大小。

确保整个脚本采用可选参数来告诉它从哪个测试编号(或其他测试标识)开始,这样当脚本的一个实例决定它占用太多内存时,它可以告诉其后继者重启。

或者,更确切地说,确保在每个测试完成后,其标识将附加到具有已知名称的某个文件中。当程序启动时,它首先读取该文件,从而知道已经运行了哪些测试。这种架构更加可靠,因为它还涵盖了测试期间程序崩溃的情况;当然,为了从这种崩溃中完全自动化恢复,你需要一个单独的监视程序和进程来负责在确定前一个崩溃时启动一个新的测试程序实例(它可以使用{{1}为此目的 - 它还需要一种方法来判断序列何时完成,例如,从测试程序正常退出可能意味着任何崩溃或退出状态!= 0表示需要启动一个新的实例)。

如果这些架构具有吸引力但你需要进一步帮助实现它们,那么只需对这个答案进行评论,我将很乐意提供示例代码 - 我不想“先发制人”地执行它以防万一-unexpressed使架构不适合您的问题。 (它可能也有助于了解您需要运行的平台)。

答案 2 :(得分:3)

我遇到了与泄漏的第三方C库相同的问题。我能想到的最干净的解决方法是分叉等待。它的优点是您甚至不必在每次运行后创建单独的进程。您可以定义批次的大小。

这是一个通用的解决方案(如果你发现了泄漏,你需要做的唯一改变就是改变run()来调用run_single_process()而不是run_forked(),你将会完成):

import os,sys
batchSize = 20

class Runner(object):
    def __init__(self,dataFeedGenerator,dataProcessor):
        self._dataFeed = dataFeedGenerator
        self._caller = dataProcessor

    def run(self):
        self.run_forked()

    def run_forked(self):
        dataFeed = self._dataFeed
        dataSubFeed = []
        for i,dataMorsel in enumerate(dataFeed,1):
            if i % batchSize > 0:
                dataSubFeed.append(dataMorsel)
            else:
                self._dataFeed = dataSubFeed
                self.fork()
                dataSubFeed = []
                if self._child_pid is 0:
                    self.run_single_process()
                self.endBatch()

    def run_single_process(self)
        for dataMorsel in self._dataFeed:
            self._caller(dataMorsel)

    def fork(self):
        self._child_pid = os.fork()

    def endBatch(self):
        if self._child_pid is not 0:
            os.waitpid(self._child_pid, 0)
        else:
            sys.exit() # exit from the child when done

这会将内存泄漏隔离到子进程。它永远不会比batchSize变量的值泄漏更多次。

答案 3 :(得分:2)

我只是将实验重构为单个函数(如果不是那样),然后从命令行接受一个实验编号,该编号调用单个实验函数。

刚刚提出的shell脚本如下:

#!/bin/bash

for expnum in 1 2 3 4 5 6 7 8 9 10 11 ; do
    python youProgram ${expnum} otherParams
done

这样,您可以保留大部分代码,这样可以清除您认为在每次实验之间发生的任何内存泄漏。

当然,最好的解决方案始终是找到并解决问题的根本原因,但正如您已经说过的那样,这不是您的选择。

虽然很难想象Python中存在内存泄漏,但我会接受你的意见 - 你可能想要至少考虑一下你错在那里的可能性。考虑在一个单独的问题中提出 ,我们可以在低优先级下工作(与此快速修复版本相反)。

更新:制作社区维基,因为问题与原始版本有所不同。我会删除答案,但事实上我仍然觉得它很有用 - 你可以像我提出的bash脚本那样对你的实验运行器做同样的事情,你只需要确保实验是单独的进程,这样就不会发生内存泄漏(如果内存泄漏在跑步者中,你将不得不进行根本原因分析并正确修复错误。)