如果我在解释器中运行,
foo = ['bar' for _ in xrange(10000000)]
我的机器上使用的实际内存达到
80.9mb
。然后,我del foo
实际内存下降,但仅下降到
30.4mb
。解释器使用4.4mb
基线,因此不向操作系统释放26mb
内存有什么好处?是因为Python在“提前计划”,以为您可能会再次使用那么多的内存吗?为什么它特别发布
50.5mb
-释放的量是多少?有没有一种方法可以强制Python释放所有已使用的内存(如果您知道不会再使用那么多的内存)?
注意
这个问题与如何我可以在Python中显式释放内存吗?
因为这个问题主要解决了基线使用的内存使用量的增加,即使解释器通过垃圾回收释放了对象(是否使用
gc.collect
)也是如此。#1 楼
堆上分配的内存可能会出现高水位标记。 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
,它没有实现空闲列表。评论
使用memory_info()代替get_memory_info()并定义了x
–阿齐兹·阿尔托(Aziz Alto)
18年5月18日在22:05
即使在Python 3中,您也可以获得10 ^ 7的整数,但是每个整数都会替换循环变量中的最后一个整数,因此它们不会一次全部存在。
–戴维斯·鲱鱼
18/09/5'2:13
我遇到了内存泄漏问题,我想这是您在这里回答的原因。但是我怎么能证明我的猜测呢?是否有任何工具可以显示已分配许多池,但仅使用了一个小块?
–ruiruige1991
19年1月31日下午4:23
#2 楼
我猜您在这里真正关心的问题是:是否有一种方法可以强制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
模块。成本是:
在某些平台上,进程启动有点慢,尤其是Windows 。我们这里所说的是毫秒,而不是分钟,如果您要让一个孩子做300秒的工作,您甚至不会注意到。但这不是免费的。
如果您确实使用了大量的临时内存,那么这样做可能会导致您的主程序被换出。当然,从长远来看,您可以节省时间,因为如果该内存永远存在,那将导致在某些时候进行交换。但这会在某些用例中将逐渐的缓慢变为非常明显的一次(和早期)延迟。
在进程之间发送大量数据可能会很慢。同样,如果您正在谈论发送超过2K的参数并返回64K的结果,您甚至不会注意到它,但是如果您发送和接收大量数据,则需要使用其他某种机制(一个文件,或者其他;文件在
mmap
中;等等。)。在进程之间发送大量数据意味着数据必须是可腌制的(或者,如果将它们粘贴到文件或共享内存中,则应为
multiprocessing
-able或理想情况下为struct
-able)。评论
很好的技巧,虽然不能解决问题:(但我真的很喜欢
– ddofborg
2015年8月23日14:43在
这是唯一对我有用的解决方案,因为使用del和gc.collect()无效。
– Brunox13
12月11日22:29
#3 楼
eryksun已经回答了问题#1,而我已经回答了问题#3(原始的#4),但现在让我们回答问题#2:为什么特别释放50.5mb -释放的量基于什么?
释放的量最终基于Python和
malloc
内部的一系列巧合,这些巧合很难预测。 > 首先,根据测量内存的方式,您可能只在测量实际映射到内存的页面。在这种情况下,无论页面何时被寻呼机换出,内存都会显示为“已释放”,即使尚未释放也是如此。
或者您可能正在测量使用中的页面,它可能会或可能不会计算已分配但从未触摸过的页面(在乐观地过度分配的系统(例如linux)上),已分配但标记为
MADV_FREE
的页面等。测量分配的页面(这实际上不是一件非常有用的事情,但这似乎是您要问的问题),并且页面实际上已经被释放,在两种情况下可能会发生这种情况:您使用了
brk
还是相当于缩小数据段(如今非常罕见),或者您已经使用munmap
或类似的方法来释放映射的段。 (从理论上讲,后者也有一个较小的变体,因为有一些方法可以释放一部分映射的段,例如,用MAP_FIXED
对其进行偷窃以立即取消映射的MADV_FREE
段。)程序不会直接在内存页面之外分配内容;他们使用
malloc
-style分配器。调用free
时,如果恰好恰好free
映射中的最后一个活动对象(或数据段的最后N个页面),则分配器只能将页面释放到OS。您的应用程序无法合理地预测甚至提前检测到它。CPython使这一过程变得更加复杂-它在
malloc
的顶部具有一个自定义的2级对象分配器,而在自定义的内存分配器之上。 (有关更详细的解释,请参见源注释。)最重要的是,即使在C API级别上,Python也要少得多,您甚至不直接控制顶级对象的释放时间。因此,当您释放一个对象时,如何知道它是否将向OS释放内存?好吧,首先,您必须知道已发布了最后一个引用(包括您不知道的任何内部引用),从而允许GC对其进行分配。 (与其他实现不同,至少CPython会在允许的情况下尽快释放对象。)这通常会在下一级向下释放至少两件事(例如,对于一个字符串,您要释放
PyString
对象,并且该字符串缓冲)。 如果要释放对象,要知道这是否导致下一级别的释放对象存储块,您必须知道对象分配器的内部状态以及其实现方式。 (除非您要重新分配该块中的最后一个东西,否则显然不会发生,即使那样,也可能不会发生。)这会导致一个
free
调用,您必须知道PyMem分配器的内部状态及其实现方式。 (同样,您必须在malloc
ed区域内分配最后一个使用中的块,即使那样,也可能不会发生。)如果在
free
ed区域中执行malloc
,要知道是否这会导致一个munmap
或等效的东西(或brk
),您必须知道malloc
的内部状态及其实现方式。而且,与其他版本不同,它是高度特定于平台的。 (同样,您通常必须在malloc
段中分配最后一个正在使用的mmap
,即使这样,也可能不会发生。)因此,如果您想了解为什么它恰好释放了50.5mb,则必须从下至上对其进行跟踪。当您执行一次或多次
malloc
调用时(为什么可能超过50.5mb),为什么free
取消映射50.5mb的页面?您必须阅读平台的malloc
,然后遍历各种表和列表以查看其当前状态。 (在某些平台上,它甚至可能利用系统级信息,如果不制作系统快照以进行脱机检查几乎是不可能捕获的,但是幸运的是,这通常不是问题。)然后,您必须在以上三个级别上执行相同的操作。因此,对该问题唯一有用的答案是“因为”。
除非您正在进行资源有限的(例如嵌入式)开发,否则您没有理由关心这些细节。
如果您正在进行资源有限的开发,知道这些细节是没有用的;您几乎必须在所有这些级别上进行最终运行,尤其是在应用程序级别上(可能在它们之间使用一个简单的,易于理解的,特定于应用程序的区域分配器)。
#4 楼
首先,您可能需要安装一下: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”终端中观察如何内存在您的系统中管理!
祝您好运!
PS我假设您正在Debian或Ubuntu系统上工作
评论
值得注意的是,这种行为并非特定于Python。通常情况是,当进程释放一些堆分配的内存时,直到该进程死亡,内存才会释放回OS。您的问题询问了多个问题,其中有些是愚蠢的,有些不适用于SO,其中有些可能是好问题。您是在问Python是否不释放内存,在什么情况下不能/不能,到底是什么底层机制,为什么这样设计,是否有任何变通办法或完全其他?
@abarnert我组合了相似的子问题。回答您的问题:我知道Python会为操作系统释放一些内存,但是为什么不全部释放内存,以及为什么要这样做呢?如果在某些情况下无法做到这一点,为什么呢?还有什么解决方法。
如何在Python中显式释放内存的可能重复项?
@jww我不这么认为。这个问题确实与为什么解释器进程甚至在通过调用gc.collect完全收集垃圾之后仍未释放内存有关。