如何强制释放字典占用的内存?

6
我正在开发一个Python脚本,用于查询多个不同的数据库以整合数据并将其持久化到另一个数据库中。 该脚本从大约15个不同的数据库中收集潜在数百万条记录的数据。为了尝试加快脚本的速度,我包含了一些缓存功能,这归结为具有某些频繁查询数据的字典。该字典包含键值对,其中键是根据数据库名称、集合名称和查询条件生成的哈希,而值是从数据库检索到的数据。例如:{123456789: {_id: '1',someField: 'someValue'}} ,其中123456789 是哈希,{_id: '1',someField: 'someValue'} 是从数据库检索到的数据。
在本地字典中保存此数据意味着,我可以在本地访问一些频繁查询的数据,而不必每次都查询数据库(这可能很慢)。如上所述,有很多查询,因此字典可能会变得相当大(几个千兆字节)。 我有一些代码使用psutil查看运行脚本的机器上可用的内存量,如果可用内存量低于某个阈值,则清除字典。清除字典的代码如下:
cached_documents.clear()
cached_documents = None
gc.collect()
cached_documents = {}

需要指出的是cached_documents是一个本地变量,它被传递到所有访问或添加缓存的方法中。不幸的是,即使调用上述代码后,Python仍然会持有大量额外的内存,这似乎并不足够释放内存。您可以在此处查看内存使用情况概要:

enter image description here

值得注意的是,在清除字典的前几次中,我们将释放大量内存回归系统,但每个后续时间似乎都变少了,此时由于Python持有大量内存,可用内存处于阈值内,因此缓存会极频繁地清除而导致内存使用率趋平。是否有办法在清空字典时强制Python正确释放内存,以避免这种趋平现象?任何提示都将不胜感激。


3
释放对象并不一定会将内存返回给操作系统,因此进程的大小不会缩小。它只是使其可用于分配给其他 Python 对象。 - Barmar
2
据我所知,可靠地将内存返回给操作系统的唯一方法是结束进程。 - user2357112
2
Python会将未使用的对象空间返回到其堆中,但整个堆被清除的可能性很小,因此它甚至不费心去判断是否可以将其返回给系统。 - tdelaney
虽然不是很相关,但你应该尝试不要将记录数据放入字典对象中。那样极其低效。使用namedtuple或者一个带有槽的类,就像这样{hash_value: namedtuple_record} - juanpa.arrivillaga
1
不,我的意思是你大缓存字典中键的值。不要再使用元组作为缓存,因为它与你的字典没有真正的关系,但如果你一开始就担心内存使用,那么使用Record = namedtuple('Record', 'id some_field'),然后cached_documents[hash_doc(document)] = Record(id, some_field_val)等... - juanpa.arrivillaga
显示剩余2条评论
1个回答

0

根据我原帖上的评论,我做了一些修改。

正如评论中提到的,Python 在进程结束之前似乎不会可靠地将内存返回给操作系统。在某些应用程序中,这意味着您可以启动一个单独的进程来处理内存密集型任务。有关详细信息,请参阅在Python中释放内存

不幸的是,在我的案例中,这并不适用,因为要点就是在需要时将数据保存在内存中。

由于Python保留了一部分分配的内存并使其对其他Python对象可用,我更新了脚本清除缓存的条件。我不再以可用系统内存为基准,而是根据缓存大小设置了清除缓存的条件。理论依据是,我可以继续填充缓存并重复使用Python持有的这段内存。通过对问题的图表中最初几次清除缓存的大致平均值进行评估,然后略微降低该数字以增加一些余地(例如,大小为10的缓存可能根据缓存内容使用不同量的内存),我找到了缓存大小的阈值。

基于可用内存清除缓存比这种方法不太安全,因为缓存可能会增长到大于系统上可用内存的大小,导致内存错误;特别是如果系统上运行需要大量内存的其他进程,然而对于我的用例来说,这是一个适当的权衡。

现在,基于缓存大小而不是可用系统内存清除缓存,我似乎能够利用Python保留内存。虽然这可能不是一个完美的答案,在我的情况下,它似乎有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接