我编写了一个Python程序,用于处理一个大输入文件并创建几百万个表示三角形的对象。算法如下:
- 读取输入文件
- 处理文件并创建一个由顶点表示的三角形列表
- 以OFF格式输出顶点列表,后跟三角形列表。三角形由顶点列表中的索引表示。
按照OFF的要求,在输出三角形之前必须打印出完整的顶点列表,这意味着在将输出写入文件之前,必须将三角形列表保存在内存中。但同时由于列表的大小,我遇到了内存错误。
最好的方法是如何告诉Python我不再需要某些数据,它可以被释放?
我编写了一个Python程序,用于处理一个大输入文件并创建几百万个表示三角形的对象。算法如下:
按照OFF的要求,在输出三角形之前必须打印出完整的顶点列表,这意味着在将输出写入文件之前,必须将三角形列表保存在内存中。但同时由于列表的大小,我遇到了内存错误。
最好的方法是如何告诉Python我不再需要某些数据,它可以被释放?
gc.collect()
显式调用垃圾回收器来释放未引用的内存。例如:import gc
gc.collect()
在使用del
标记要丢弃的内容后,您应该执行此操作:
del my_array
del my_object
gc.collect()
gc.collect()
实际上有助于避免内存碎片化,从而有助于保持性能。我曾看到这样做可以显著改善程序运行时间(据我所记,大约提高了20%)。 - RobMgc.collect()
将内存使用量从 1.7GB 减少到了 500MB。 - Johndel my_array
然后跟随着gc.collect()
才能真正释放内存,确保我的程序能够继续加载下一个数组。 - David很不幸地(根据你所使用的Python版本和发布版本),某些类型的对象使用“自由列表”,这是一种巧妙的本地优化,但可能会导致内存碎片化,具体表现为将越来越多的内存“指定”为仅用于某种类型的对象,因此无法用于“总基金”。
确保大量但临时使用的内存在完成后返回系统所有资源的唯一可靠方法是使其发生在子进程中,由该子进程执行内存密集型任务然后终止。在这种情况下,操作系统将履行其职责,并欣然回收子进程可能占用的所有资源。幸运的是,在现代版本的Python中,multiprocessing
模块使得这种操作(过去非常麻烦)变得不那么糟糕。
在您的用例中,似乎最好的方式是让子进程累积一些结果,同时确保这些结果对主进程可用的方法是使用半临时文件(半临时是指不是在关闭时自动消失的文件,而是您需要在处理完它们后显式地删除它们的普通文件)。
multiprocessing.Manager
代替文件实现共享状态,这是一个小例子,详情请查看链接:https://dev59.com/OGAg5IYBdhLWcg3wI4ER#24126616 - user4815162342gc.collect()
呢? - Charlie Parkerx = [obj1, obj2, ...obj20]
。为了释放内存,可以采取以下任何一种措施:(1) del x
(2) x=[]
(3) del x[:]
。只是对于方法(1),变量 x
被删除且不再可访问,因此列表 x
的内存也将被释放。而对于方法 (2) 和 (3),x
仍然可访问并且仍然占用内存。 - AnnieFromTaiwangc.collect()
不是必需的,因为列表中的内容是对象,只要它们的引用计数为0,对象的内存就会立即释放。只有当列表的内容是整数或浮点数时,才需要执行 gc.collect()
。 - AnnieFromTaiwangc.collect()
呢? - Charlie ParkerPython自带垃圾回收功能,所以如果你减小列表的大小,它将重新回收内存。你也可以使用"del"语句完全删除一个变量:
biglist = [blah,blah,blah]
#...
del biglist
(del
可以帮助你标记对象为可删除状态,当没有其他参考引用它们时。通常情况下,CPython解释器会保留这段内存以备后用,所以你的操作系统可能看不到这些“释放”的内存。)
如果使用更紧凑的数据结构,也许您在首次就不会遇到任何内存问题。
因此,数字列表比标准array
模块或第三方numpy
模块使用的格式要少得多。将顶点放在NumPy 3xN数组中,并将三角形放在N元素数组中,可以节省内存。
del
并没有做任何重新分配对象所有名称引用的不同值的事情。 - Charles Duffydel
从Python的角度释放内存,但通常不从C运行时库或操作系统的角度释放。参考资料:https://dev59.com/v1wY5IYBdhLWcg3w0KrL#32167625,http://effbot.org/pyfaq/why-doesnt-python-release-the-memory-when-i-delete-a-large-object.htm。 - Eric O. Lebigotdel
与退出作用域、重新赋值等同样有效。 - Charles Duffy你不能显式地释放内存。你需要做的是确保你不保留对象的引用。那么它们将被垃圾收集,从而释放内存。
在你的情况下,当你需要大量列表时,通常需要重新组织代码,通常使用生成器/迭代器代替。这样你就根本不需要在内存中拥有大型列表。
我曾经遇到过类似的问题,需要从文件中读取图形。处理过程包括计算一个200 000x200 000的浮点矩阵(逐行处理),但该矩阵无法放入内存。尝试在计算之间使用gc.collect()
释放内存解决了与内存相关的问题,但导致性能问题:我不知道为什么,即使使用的内存量保持恒定,每次新调用gc.collect()
所需时间都比上一次多一些。因此,很快垃圾收集占据了大部分计算时间。
为了解决内存和性能问题,我采用了一种多线程技巧,这是我在某个地方读到的(很抱歉,我找不到相关的帖子了)。在此之前,我正在一个大型for
循环中逐行读取文件,处理它,并偶尔运行gc.collect()
来释放内存空间。现在我调用一个函数,在新线程中读取和处理文件的一块内容。一旦线程结束,内存就会自动释放,而不会出现奇怪的性能问题。
实际上,它的工作原理如下:
from dask import delayed # this module wraps the multithreading
def f(storage, index, chunk_size): # the processing function
# read the chunk of size chunk_size starting at index in the file
# process it using data in storage if needed
# append data needed for further computations to storage
return storage
partial_result = delayed([]) # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100 # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
# we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
partial_result = delayed(f)(partial_result, index, chunk_size)
# no computations are done yet !
# dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
# passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
# it also allows you to use the results of the processing of the previous chunks in the file if needed
# this launches all the computations
result = partial_result.compute()
# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided
//
而不是#来进行注释。 - user4396006正如其他回答所说,即使Python代码不再使用内存(因此gc.collect()
没有释放任何内容),在长时间运行的程序中,Python也可以避免将内存释放到操作系统中。 但是,如果您在Linux上,可以尝试通过直接调用libc函数malloc_trim
来释放内存(man page)。
类似于:
import ctypes
libc = ctypes.CDLL("libc.so.6")
libc.malloc_trim(0)
lib.malloc_trim(var)
吗? - Charlie Parkermalloc_trim
不是这样工作的(请参阅手册页)。此外,我认为libc不知道Python变量名的任何信息,因此这种方法不适合处理变量。 - Joril其他人已经发布了一些方法,你可能能够“劝说”Python解释器释放内存(或避免出现内存问题)。你应该先尝试他们的想法。然而,我认为直接回答你的问题很重要。
实际上并没有直接告诉Python释放内存的方法。事实是,如果你想拥有那么低的控制级别,你需要用C或C++编写扩展程序。
话虽如此,这里有一些工具可以帮助处理这个问题: