在下面的例子中,我有一些关于内存使用的相关问题。
如果我在翻译程序中运行
foo = ['bar' for _ in xrange(10000000)]
我的机器上使用的实际内存达到80.9mb
。我那时
del foo
真正的内存会减少,但只会减少到30.4mb
。解释器使用4.4mb
基线,那么不向操作系统释放26mb
内存有什么好处呢?是不是因为Python“提前计划”,认为您可能会再次使用那么多内存?
为什么它会释放50.5mb
特别是-释放的数量是基于什么?
有没有办法强迫Python释放所有使用过的内存(如果您知道您不会再使用那么多内存的话)?
注意
这个问题不同于How can I explicitly free memory in Python?
因为这个问题主要处理的是,即使解释器通过垃圾收集释放了对象(使用gc.collect
或不使用gc.collect
)之后,内存使用量也会从基线增加。
我猜你真正关心的问题是:
不,没有。但是有一个简单的解决方法:子进程。
如果您需要500MB的临时存储空间5分钟,但之后您需要再运行2个小时,并且再也不会接触到那么多内存,则生成一个子进程来执行内存密集型工作。当子进程消失时,内存被释放。
这不是完全的琐碎和免费,但它是相当容易和便宜的,这通常是足够好的贸易是值得的。
首先,创建子进程的最简单方法是使用^{} (或者,对于3.1及更早版本,使用PyPI上的^{} backport):
如果需要更多的控制,请使用^{} 模块。
费用包括:
mmap
ped或其他;中的共享内存api等)。struct
-可pickle的,或者理想情况下是ctypes
-可pickle的)。埃里克森回答了问题1,我回答了问题3(原来的问题4),现在我们回答问题2:
它的基础是,最终,Python和
malloc
内部的一系列巧合,这些巧合很难预测。首先,取决于您如何测量内存,您可能只测量实际映射到内存中的页。在这种情况下,当页面被寻呼机调出时,内存将显示为“已释放”,即使它没有被释放。
或者,您可能正在测量正在使用的页面,这些页面可能计算也可能不计算已分配但从未接触过的页面(在优化过度分配的系统上,如linux)、已分配但已标记为
MADV_FREE
的页面等如果您确实在测量已分配的页(这实际上不是一件非常有用的事情,但似乎正是您所要求的),并且页确实已被释放,那么在两种情况下可能会发生这种情况:要么您使用了
brk
,要么相当于缩小了数据段(现在非常罕见),或者你用munmap
或者类似的方法来释放一个映射片段。(理论上,后者也有一个小的变体,因为有一些方法可以释放一个映射段的一部分,例如,用MAP_FIXED
来窃取一个立即取消映射的MADV_FREE
段。)但是大多数程序不会直接从内存页中分配内容;它们使用
malloc
样式的分配器。调用free
时,如果恰好free
正在映射中的最后一个活动对象(或数据段的最后N个页面),分配器只能向操作系统释放页面。您的应用程序无法合理地预测这一情况,甚至无法检测到它是提前发生的。CPython使这变得更加复杂,它在
malloc
上的自定义内存分配器之上有一个自定义的2级对象分配器。(请参见the source comments以获得更详细的解释)除此之外,即使在C API级别,更不用说Python,您甚至无法直接控制顶级对象何时被释放。所以,当你释放一个对象时,你如何知道它是否会向操作系统释放内存?好吧,首先你必须知道你已经发布了最后一个引用(包括你不知道的任何内部引用),允许GC释放它。(与其他实现不同的是,至少CPython会在允许的情况下尽快释放一个对象。)这通常会在下一级释放至少两个对象(例如,对于字符串,您释放的是
PyString
对象和字符串缓冲区)。如果您要解除分配一个对象,要知道这是否会导致下一个级别降低以解除分配一个对象存储块,您必须知道对象分配器的内部状态,以及它是如何实现的。(很明显,除非你正在释放块中的最后一个东西,否则它是不可能发生的,即使这样,它也可能不会发生。)
如果您要释放一个对象存储块,要知道这是否会导致一个
free
调用,您必须知道PyMem分配器的内部状态,以及它是如何实现的。(同样,您必须在malloc
ed区域内释放最后一个正在使用的块,即使这样,也可能不会发生。)如果你要知道这是否会导致一个
munmap
或等效的(或brk
)区域,你必须知道malloc
的内部状态,以及它是如何实现的。而这一个,与其他的不同,是高度特定于平台的。(而且,通常必须在mmap
段中释放最后一个正在使用的malloc
,即使这样,也可能不会发生。)所以,如果你想知道为什么它会发布50.5mb,你必须从下往上追踪它。当您进行一个或多个
free
调用(可能超过50.5mb)时,为什么要malloc
取消映射值为50.5mb的页面?您必须阅读平台的malloc
,然后n遍历各个表和列表以查看其当前状态。(在某些平台上,它甚至可能利用系统级信息,如果不制作系统快照进行脱机检查,几乎不可能捕获这些信息,但幸运的是,这通常不是问题。)然后,您必须在上述3个级别上执行相同的操作。所以,唯一有用的答案就是“因为”
除非您正在进行资源有限(例如,嵌入式)开发,否则您没有理由关心这些细节。
如果您正在进行资源有限的开发,那么了解这些细节是没有用的;您几乎必须在所有这些级别上执行一次结束运行,特别是在应用程序级别上需要的内存(可能中间有一个简单的、很好理解的、特定于应用程序的区域分配器)。
堆上分配的内存可能会受到高水位线的影响。Python对4个KiB池中分配小对象(
PyObject_Malloc
)的内部优化使这一点变得复杂,这些KiB池的分配大小是8字节的倍数——高达256字节(3.3中为512字节)。池本身位于256个KiB竞技场中,因此如果仅使用一个池中的一个块,则不会释放整个256个KiB竞技场。在Python3.3中,小对象分配器被切换到使用匿名内存映射而不是堆,因此它在释放内存方面应该表现得更好。此外,内置类型维护以前分配的对象的自由列表,这些对象可能使用也可能不使用小对象分配器。
int
类型使用自己分配的内存维护一个freelist,清除它需要调用PyInt_ClearFreeList()
。这可以通过执行完整的gc.collect
来间接调用。像这样试试,告诉我你得到了什么。这是psutil.Process.memory_info的链接。
输出:
编辑:
我切换到相对于进程VM大小的测量,以消除系统中其他进程的影响。
当顶部的连续可用空间达到常量、动态或可配置阈值时,C运行时(例如glibc、msvcrt)会收缩堆。使用glibc,您可以使用^{} (M_TRIM_THRESHOLD)对其进行优化。考虑到这一点,如果堆比您
free
的块收缩得更多——甚至更多——也就不足为奇了。在3.x中
range
不会创建列表,因此上面的测试不会创建1000万个int
对象。即使是这样,3.x中的int
类型基本上是一个2.xlong
,它没有实现自由列表。相关问题 更多 >
编程相关推荐