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



如果我在解释器中运行,

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


我的机器上使用的实际内存达到80.9mb。然后,我

del foo


实际内存下降,但仅下降到30.4mb。解释器使用4.4mb基线,因此不向操作系统释放26mb内存有什么好处?是因为Python在“提前计划”,以为您可能会再次使用那么多的内存吗?

为什么它特别发布50.5mb-释放的量是多少?
有没有一种方法可以强制Python释放所有已使用的内存(如果您知道不会再使用那么多的内存)?

注意
这个问题与如何我可以在Python中显式释放内存吗?
因为这个问题主要解决了基线使用的内存使用量的增加,即使解释器通过垃圾回收释放了对象(是否使用gc.collect)也是如此。

评论

值得注意的是,这种行为并非特定于Python。通常情况是,当进程释放一些堆分配的内存时,直到该进程死亡,内存才会释放回OS。

您的问题询问了多个问题,其中有些是愚蠢的,有些不适用于SO,其中有些可能是好问题。您是在问Python是否不释放内存,在什么情况下不能/不能,到底是什么底层机制,为什么这样设计,是否有任何变通办法或完全其他?
@abarnert我组合了相似的子问题。回答您的问题:我知道Python会为操作系统释放一些内存,但是为什么不全部释放内存,以及为什么要这样做呢?如果在某些情况下无法做到这一点,为什么呢?还有什么解决方法。

如何在Python中显式释放内存的可能重复项?

@jww我不这么认为。这个问题确实与为什么解释器进程甚至在通过调用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系统上工作