7

我正在从面向对象的数据库中读取一组对象(表,如 sqlite3 表或数据帧),其中大部分都足够小,Python 垃圾收集器可以毫无意外地处理。但是,当它们变得更大(小于 10 MB)时,GC 似乎无法跟上。

伪代码如下所示:

walk = walkgenerator('/path')
objs = objgenerator(walk)
with db.transaction(bundle=True, maxSize=10000, maxParts=10): 
    oldobj = None
    oldtable = None
    for obj in objs:
        currenttable = obj.table
        if oldtable and oldtable in currenttable:
            db.delete(oldobj.path)
        del oldtable
        oldtable = currenttable
        del oldobj
        oldobj = obj
        if not count % 100:
            gc.collect()

我正在寻找一种优雅的方式来管理内存,同时允许 Python 在可能的情况下处理它。

也许令人尴尬的是,我尝试使用 del 来帮助清理引用计数。

我已经在我的 for 循环中以不同的模数尝试了 gc.collect():

  • 100(无差异),
  • 1(大大减慢循环,我仍然会收到某种类型的内存错误),
  • 3(循环仍然很慢,但内存最终还是会爆炸)

建议表示赞赏!!!

特别是,如果你能给我一些工具来帮助自省。我在这里使用过 Windows 任务管理器,它似乎或多或少地随机出现内存泄漏。我尽可能地限制了交易规模,这似乎有点帮助。

4

1 回答 1

5

这里没有足够的信息可以说太多,但我不得不说的内容不适合评论,所以我会在这里发布;-)

首先,也是最重要的,在 CPython 中,垃圾收集主要基于引用计数。 gc.collect()除非引用循环中涉及垃圾对象(A可以通过遵循可传递的指针链从自身访问对象),否则不会为您做任何事情(燃烧时间除外A)。您在显示的代码中没有创建引用循环,但数据库层可能会这样做。

那么,在你运行之后gc.collect(),内存使用量会下降吗?如果没有,运行它是没有意义的。

我预计数据库层最有可能持有对对象的引用超过必要的时间,但深入研究需要深入了解数据库层如何实现的确切细节。

获取线索的一种方法是打印sys.getrefcount()应用于各种大对象的结果:

>>> import sys
>>> bigobj = [1] * 1000000
>>> sys.getrefcount(bigobj)
2

正如文档所说,结果通常比您希望的要大 1,因为getrefcount()' 的参数的引用计数临时增加 1 仅仅是因为它(临时)用作参数。

因此,如果您看到 refcount 大于 2,del则不会释放该对象。

另一种获取线索的方法是将对象传递给gc.get_referrers(). 这将返回直接引用参数的对象列表(前提是引用者参与了 Python 的循环 gc)。

顺便说一句,您需要更清楚“似乎不起作用”和“最终爆炸”的意思。猜不透。究竟了什么问题?例如,是否MemoryError提出?还有什么?回溯通常会产生大量有用的线索。

于 2013-11-22T20:02:35.360 回答