在以下示例中,我有一些与内存使用有关的问题。
如果我在翻译中运行,
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
或不使用)后,从基线增加内存使用量。
答案 0 :(得分:116)
我猜你真正关心的问题是:
有没有办法强制Python释放所有使用的内存(如果你知道你不会再使用那么多内存)?
不,没有。但是有一个简单的解决方法:子进程。
如果你需要500MB的临时存储时间为5分钟,但在此之后你需要再运行2个小时并且不会再次接触那么多内存,产生子进程来执行内存密集型工作。当子进程消失时,内存将被释放。
这不是完全无关紧要和免费的,但它非常简单和便宜,通常足以让交易变得有价值。
首先,创建子进程的最简单方法是使用concurrent.futures
(或者,对于3.1及更早版本,使用PyPI上的futures
反向端口):
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
result = executor.submit(func, *args, **kwargs).result()
如果您需要更多控制,请使用multiprocessing
模块。
费用如下:
mmap
ped或其他; multiprocessing
中的共享内存API;等。)。struct
- 能够或理想ctypes
- 能够。 / LI>
答案 1 :(得分:79)
堆上分配的内存可能会受到高水位影响。 Python的内部优化在4 KiB池中分配小对象(PyObject_Malloc
),这很复杂,分配大小为8字节的倍数 - 最多256字节(3.3字节为512字节)。池本身在256 KiB竞技场中,因此如果使用一个池中的一个块,则整个256 KiB竞技场将不会被释放。在Python 3.3中,小对象分配器被切换为使用匿名内存映射而不是堆,因此它应该在释放内存时表现更好。
此外,内置类型维护以前分配的对象的空闲列表,这些对象可能使用也可能不使用小对象分配器。 int
类型维护一个具有自己分配的内存的空闲列表,清除它需要调用PyInt_ClearFreeList()
。这可以通过完整gc.collect
间接调用。
尝试这样,并告诉我你得到了什么。这是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
的块减少更多 - 甚至更多 - 也就不足为奇了。
在3.x range
中没有创建列表,因此上面的测试不会创建1000万个int
个对象。即使它确实如此,3.x中的int
类型基本上是2.x long
,它不实现空闲列表。
答案 2 :(得分:28)
为什么它特别释放50.5mb - 基于发布的金额是多少?
它的基础是,最终,Python和malloc
内部的一系列巧合很难预测。
首先,根据您测量内存的方式,您可能只测量实际映射到内存中的页面。在这种情况下,只要页面被寻呼机换出,内存就会显示为“已释放”,即使它尚未被释放。
或者您可能正在测量使用中的页面,这些页面可能会也可能不会计算已分配但从未触及的页面(在乐观过度分配的系统上,如linux),已分配但已标记的页面{{1}等等。
如果你真的在测量已分配的页面(实际上这不是一件非常有用的事情,但它似乎是你所要求的),并且页面确实已被释放,这可能发生在两种情况:您使用MADV_FREE
或同等数据来缩小数据段(现在非常罕见),或者您使用brk
或类似内容来释放映射的段。 (理论上也有后者的一个小变体,因为有一些方法可以释放映射片段的一部分 - 例如,使用munmap
为你立即取消映射的MAP_FIXED
段窃取它。)
但是大多数程序都没有直接从内存页面中分配内容;他们使用MADV_FREE
式的分配器。当您调用malloc
时,如果您恰好free
映射中的最后一个活动对象(或数据段的最后N页),则分配器只能将页面释放到操作系统。你的应用程序无法合理地预测这一点,甚至无法提前发现它。
CPython使这变得更加复杂 - 它在free
之上的自定义内存分配器之上有一个自定义的2级对象分配器。 (有关更详细的说明,请参阅the source comments。)除此之外,即使在C API级别,更不用说Python,您甚至无法直接控制何时释放顶级对象。
所以,当你发布一个对象时,你怎么知道它是否会向操作系统释放内存?好吧,首先你必须知道你已经发布了最后一个引用(包括你不知道的任何内部引用),允许GC解除分配。 (与其他实现不同,至少CPython会在允许的情况下解除对象的释放。)这通常会在下一级别释放至少两个东西(例如,对于字符串,您将释放malloc
个对象,和字符串缓冲区)。
如果您执行解除分配对象,要知道这是否导致下一级别解除分配对象存储块,您必须知道对象分配器的内部状态,以及如何它实施了。 (显然不可能发生,除非你要解除块中的最后一件事,即便如此,也可能不会发生。)
如果您执行解除分配对象存储块,要知道这是否会导致PyString
调用,您必须知道PyMem分配器的内部状态,以及它是如何调用的实现。 (同样,你必须在free
ed区域内释放最后一个使用中的块,即使这样,也可能不会发生。)
如果您执行 malloc
free
ed区域,要知道这是否会导致malloc
或同等(或munmap
),必须知道brk
的内部状态,以及它是如何实现的。与其他产品不同,这一产品具有高度的平台特性。 (同样,您通常必须在malloc
段内释放最后一个正在使用的malloc
,即使这样,也可能不会发生。)
所以,如果你想了解为什么它恰好发布了50.5mb,你将不得不从下往上追踪它。为什么mmap
在您执行一个或多个malloc
调用(可能略高于50.5mb)时,取消映射50.5mb的页面值?您必须阅读平台的free
,然后遍历各种表和列表以查看其当前状态。 (在某些平台上,它甚至可以利用系统级信息,如果不制作系统快照以进行离线检测,则几乎不可能捕获,但幸运的是,这通常不是问题。)然后你必须在上面的3个级别做同样的事情。
因此,对这个问题唯一有用的答案是“因为”。
除非您正在进行资源有限(例如嵌入式)开发,否则您没有理由关注这些细节。
如果你 进行资源有限的开发,那么了解这些细节是没用的;你几乎必须围绕所有这些级别进行最终运行,特别是malloc
你在应用程序级别需要的内存(可能只有一个简单的,易于理解的,特定于应用程序的区域分配器)。
答案 3 :(得分:1)
首先,您可能要安装一眼:
sudo apt-get install python-pip build-essential python-dev lm-sensors
sudo pip install psutil logutils bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard
sudo pip install glances
然后在终端中运行它!
glances
在您的Python代码中,在文件的开头添加以下内容:
import os
import gc # Garbage Collector
使用“ Big”变量(例如:myBigVar)后,您要为其释放内存,请在python代码中编写以下内容:
del myBigVar
gc.collect()
在另一个终端中,运行python代码,并在“ glances”终端中观察如何在系统中管理内存!
祝你好运!
P.S。我假设您正在Debian或Ubuntu系统上工作