在Python中释放内存

在下面的例子中,我有一些关于内存使用情况的相关问题。

  1. 如果我在翻译中跑步,

    foo = ['bar' for _ in xrange(10000000)] 

    我的机器上使用的真实内存高达80.9mb 。 那我呢,

     del foo 

    真实记忆下降,但只有30.4mb 。 解释器使用了4.4mb基线,因此在向操作系统释放26mb的内存方面有什么优势? 是因为Python“提前计划”,认为你可能再次使用那么多的内存?

  2. 为什么它特别释放50.5mb – 基于什么数量释放?

  3. 有没有办法强制Python释放所有使用的内存(如果你知道你不会再使用那么多的内存)?

分配在堆上的内存可能会受到高水位痕迹的影响。 Python的内部优化用于在4个KiB池中分配小对象( PyObject_Malloc ),其分配大小为8个字节的倍数 – 高达256个字节(3.3中的512个字节)。 游泳池本身在256 KiB的舞台上,所以如果只使用一个游泳池中的一个游戏块,整个256 KiB的游戏场将不会被释放。 在Python 3.3中,小对象分配器被切换到使用匿名内存映射而不是堆,所以它应该在释放内存时更好地执行。

此外,内置types保留了以前分配的对象的空闲列表,这些对象可能会或可能不会使用小对象分配器。 inttypes用自己分配的内存维护一个freelist,清除它需要调用PyInt_ClearFreeList() 。 这可以通过完整的gc.collect间接gc.collect

试试这样,告诉我你得到了什么。 这是psutil的链接。

 import os import gc import psutil proc = psutil.Process(os.getpid()) gc.collect() mem0 = proc.get_memory_info().rss # create approx. 10**7 int objects and pointers foo = ['abc' for x in range(10**7)] mem1 = proc.get_memory_info().rss # unreference, including x == 9999999 del foo, x mem2 = proc.get_memory_info().rss # collect() calls PyInt_ClearFreeList() # or use ctypes: pythonapi.PyInt_ClearFreeList() gc.collect() mem3 = proc.get_memory_info().rss pd = lambda x2, x1: 100.0 * (x2 - x1) / mem0 print "Allocation: %0.2f%%" % pd(mem1, mem0) print "Unreference: %0.2f%%" % pd(mem2, mem1) print "Collect: %0.2f%%" % pd(mem3, mem2) print "Overall: %0.2f%%" % pd(mem3, mem0) 

输出:

 Allocation: 3034.36% Unreference: -752.39% Collect: -2279.74% Overall: 2.23% 

编辑:

我切换到相对于进程VM大小的测量,以消除系统中其他进程的影响。

C运行时(例如glibc,msvcrt)在顶部的连续可用空间达到恒定的,dynamic的或可configuration的阈值时收缩堆。 有了glibc,你可以用mallopt (M_TRIM_THRESHOLD)来调整它。 考虑到这一点,如果这个堆比你free的块更多,甚至更多,那就不足为奇了。

在3.x range内不会创build一个列表,所以上面的testing不会创build1000万个int对象。 即使这样做,3.x中的inttypes基本上是一个2.x long ,它不会实现freelist。

我猜你真正关心的问题是:

有没有办法强制Python释放所有使用的内存(如果你知道你不会再使用那么多的内存)?

不,那里没有。 但是有一个简单的解决方法:subprocess。

如果你需要5分钟500MB的临时存储空间,但是之后你需要再运行2个小时,并且不会再触及那么多的内存,产生一个subprocess来完成内存密集型的工作。 当subprocess消失时,内存得到释放。

这并不是完全平凡和自由的,但是它非常简单和便宜,通常对于交易来说是有价值的。

首先,创build一个subprocess的最简单方法是使用concurrent.futures (或者对于3.1及更早的版本,PyPI的futures backport):

 with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor: result = executor.submit(func, *args, **kwargs).result() 

如果您需要更多的控制,请使用multiprocessing模块。

成本是:

  • 在某些平台上,进程启动比较慢,特别是Windows。 我们在这里说毫秒,而不是几分钟,如果你让一个孩子做了300秒的工作,你甚至不会注意到它。 但这不是免费的。
  • 如果您使用的大量临时内存真的很大 ,那么执行此操作可能会导致您的主程序被换出。 当然,从长远来看,你可以节省时间,因为如果这种记忆永远存在下去,就必须导致在某个时刻进行交换。 但是在某些使用情况下,这可能会使缓慢的缓慢变为非常明显的一次性(及早)延迟。
  • 在进程之间发送大量数据可能会很慢。 同样,如果你正在讨论发送超过2K的参数并返回64K的结果,你甚至不会注意到它,但是如果你发送和接收大量的数据,你会想要使用其他一些机制(一个文件, mmap或其他; multiprocessing的共享内存API等)。
  • 在进程之间发送大量的数据意味着数据必须是可选的(或者,如果你把它们放在一个文件或共享内存中,可以使用structable或理想的ctypesable)。

eryksun已经回答了问题#1,我已经回答了问题#3(原来的#4),但现在让我们回答问题2:

为什么它特别释放50.5mb – 基于什么数量释放?

它所基于的最终是Python和malloc中的一系列巧合,这些巧合很难预测。

首先,根据你如何测量内存,你可能只能测量实际映射到内存中的页面。 在这种情况下,任何时候页面被页面换出,内存将显示为“释放”,尽pipe它还没有被释放。

或者,您可能正在测量使用中的页面,这些页面可能会或可能不会计算分配但从未触及的页面(在乐观分配过多的系统上,如linux),已分配但标记为MADV_FREE页面等。

如果你真的在测量分配的页面(这实际上不是一个非常有用的事情,但它似乎是你问的),页面已经真正被释放,在这种情况下可能发生两种情况:已经使用brk或相当于缩小数据段(现在非常罕见),或者您已经使用munmap或类似的来释放映射的段。 (对于后者,理论上还有一个小的变体,因为有一些方法可以释放映射段的一部分,例如,用立即取消映射的MADV_FREE段的MAP_FIXED来窃取它。

但大多数程序不直接分配内存页面的东西; 他们使用malloc风格的分配器。 当你打电话时,分配器只能释放映射中的最后一个活动对象(或者在数据段的最后N个页面中)才能释放页面到操作系统。 您的应用程序无法合理预测这一点,甚至无法预测事件是否发生。

CPython使这变得更加复杂 – 它在malloc之上的自定义内存分配器之上有一个自定义的2级对象分配器。 (关于更详细的解释,请参阅源代码注释 。)最重要的是,即使在C API级别,更less的Python也不会直接控制何时解除顶级对象。

所以,当你释放一个对象时,你怎么知道它是否会释放内存到操作系统? 那么首先你必须知道你已经发布了最后一个引用(包括你不知道的内部引用),允许GC去释放它。 (与其他实现不同,至lessCPython会在允许的情况下尽快释放对象)。这通常会在下一级释放至less两件事情(例如,对于string,您释放PyString对象,并且string缓冲)。

如果你确实释放了一个对象,为了知道这是否导致下一个级别释放一个对象存储块,你必须知道对象分配器的内部状态,以及它是如何实现的。 (除非你把块中的最后一个东西解除分配,否则显然是不可能发生的,即使这样,也可能不会发生。)

如果您释放一个对象存储块,要知道这是否导致free调用,您必须知道PyMem分配器的内部状态以及如何实现。 (同样,你必须释放一个malloc区域内的最后使用块,即使这样,也可能不会发生。)

如果你free一个malloc ed区域,要知道这是否导致一个munmap或同等的(或brk ),你必须知道malloc的内部状态,以及如何实现。 而这个与其他的不同,是高度特定于平台的。 (再次,你通常必须在mmap片段中释放最后使用的malloc ,即使这样,也不一定会发生。)

所以,如果你想知道为什么要发布正好50.5mb,你将不得不从底部追踪它。 为什么当你做了一个或多个free电话(可能超过50.5mb), malloc取消了50.5mb的页面? 您必须阅读您的平台的malloc ,然后遍历各个表和列表以查看其当前状态。 (在某些平台上,它甚至可能会使用系统级的信息,如果不制作系统的快照来离线检查,捕获几乎是不可能的,但幸运的是,这通常不是问题。)然后,您必须在3层以上做同样的事情。

所以,这个问题唯一有用的答案是“因为”。

除非您正在进行资源有限(如embedded式)开发,否则您没有理由关心这些细节。

如果你正在进行资源有限的开发,知道这些细节是没有用的; 你几乎必须围绕所有这些层次做一个末端运行,特别是在应用程序层次上mmap你需要的内存(可能只需要一个简单的,很好理解的,特定于应用程序的区域分配器)。