在以下示例中,我有一些有关内存使用的相关问题。
foo = ['bar' for _ in xrange(10000000)]
我的机器上使用的实际内存最高为80.9mb。那我
del foo
实际内存下降,但仅限于30.4mb。解释器使用4.4mb基线,因此不26mb向OS 释放内存有什么好处?是否因为Python正在“提前计划”,以为你可能会再次使用那么多的内存?
30.4mb
4.4mb
26mb
OS
它为什么50.5mb特别释放- 释放的量基于什么?
50.5mb
有没有一种方法可以强制Python释放所有已使用的内存(如果你知道不会再使用那么多的内存)?
注意 此问题不同于我如何在Python中显式释放内存? 因为这个问题主要解决了内存使用量相对于基线的增加,即使解释器通过垃圾回收(使用gc.collect或不使用)释放了对象之后。
gc.collect
堆上分配的内存可能会出现高水位标记。Python PyObject_Malloc在4个KiB池中分配小对象()的内部优化使情况复杂化,分类为8字节倍数的分配大小-最多256字节(3.3中为512字节)。池本身位于256 KiB竞技场中,因此,如果仅在一个池中使用一个块,则不会释放整个256 KiB竞技场。在Python 3.3中,小型对象分配器已切换为使用匿名内存映射而不是堆,因此它在释放内存方面应表现更好。
Python PyObject_Malloc
此外,内置类型维护以前分配的对象的空闲列表,这些对象可能使用也可能不使用小对象分配器。该int类型维护一个具有自己分配的内存的空闲列表,要清除它,需要调用PyInt_ClearFreeList()。可以通过做一个full来间接地调用它gc.collect。
int
PyInt_ClearFreeList()
这样尝试,然后告诉我你得到了什么。这是psutil.Process.memory_info的链接。
psutil.Process.memory_info
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)会缩小堆。使用glibc,你可以使用mallopt(M_TRIM_THRESHOLD)进行调整。鉴于此,如果堆的收缩量比你的块减少的量更大,甚至更多,也就不足为奇了free。
glibc,msvcrt
glibc
mallopt(M_TRIM_THRESHOLD)
在3.x range中不会创建列表,因此上面的测试不会创建1000万个int对象。即使这样做,int3.x中的类型也基本上是2.x long,它没有实现自由列表。
3.x range
2.x long
不,那里没有。但是有一个简单的解决方法:子进程。
如果需要5分钟的500MB临时存储,但是之后又需要运行2个小时,并且不会再碰到那么多的内存,请生成一个子进程来进行占用大量内存的工作。当子进程消失时,内存将被释放。
这不是完全琐碎和免费的,但是它很容易且便宜,通常足以使交易值得。
首先,最简单的创建子进程的方法是concurrent.futures(对于3.1及更早版本,futures使用PyPI上的backport):
concurrent.futures
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor: result = executor.submit(func, *args, **kwargs).result()
如果需要更多控制,请使用该multiprocessing模块。
费用是:
mmapPed
API multiprocessing
ctypes