在Python中释放内存

Jar*_*red 122 python memory-management

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

  1. 如果我在翻译中跑,

    foo = ['bar' for _ in xrange(10000000)]
    
    Run Code Online (Sandbox Code Playgroud)

    我机器上使用的真实内存最多80.9mb.然后,我

    del foo
    
    Run Code Online (Sandbox Code Playgroud)

    真正的记忆力下降,但仅限于30.4mb.解释器使用4.4mb基线,那么不26mb向OS 释放内存的优势是什么?是因为Python"提前规划",认为你可能会再次使用那么多内存吗?

  2. 为什么它会50.5mb特别释放- 基于此发布的金额是多少?

  3. 有没有办法强制Python释放所有使用的内存(如果你知道你不会再使用那么多内存)?

注意 这个问题不同于我如何在Python中明确释放内存? 因为这个问题主要处理从基线增加内存使用量,即使在解释器通过垃圾收集(使用gc.collect或不使用)释放对象之后.

aba*_*ert 122

我猜这里你真正关心的问题是:

有没有办法强制Python释放所有使用的内存(如果你知道你不会再使用那么多内存)?

不,那里没有.但有一个简单的解决方法:子进程.

如果你需要500MB的临时存储空间5分钟,但在那之后你需要再运行2个小时并且不会再次触及那么多内存,产生一个子进程来进行内存密集型工作.当子进程消失时,内存将被释放.

这不是完全无关紧要和免费的,但它非常简单和便宜,通常足以让交易变得有价值.

首先,创建子进程的最简单方法是使用concurrent.futures(或者,对于3.1及更早版本,futures使用PyPI上的backport):

with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
    result = executor.submit(func, *args, **kwargs).result()
Run Code Online (Sandbox Code Playgroud)

如果您需要更多控制,请使用该multiprocessing模块.

费用是:

  • 在某些平台上,进程启动有点慢,特别是Windows.我们在这里谈论毫秒,而不是几分钟,如果你让一个孩子做300秒的工作,你甚至都不会注意到它.但它不是免费的.
  • 如果您使用的大量临时内存确实很大,执行此操作可能会导致主程序被换出.当然,从长远来看,你节省了时间,因为如果那个记忆永远存在,它将不得不导致在某些时候交换.但是,在某些用例中,这可能会逐渐缓慢变为非常明显的一次性(和早期)延迟.
  • 在进程之间发送大量数据可能很慢.同样,如果您正在讨论发送超过2K的参数并获得64K结果,您甚至不会注意到它,但如果您要发送和接收大量数据,您将需要使用其他一些机制(文件,mmapped或其他;共享内存API multiprocessing;等等).
  • 在进程之间发送大量数据意味着数据必须是可选择的(或者,如果您将它们粘贴在文件或共享内存中,struct可以使用或者理想情况ctypes下).


Ery*_*Sun 85

在堆上分配的内存可能会受到高水位影响.Python的内部优化PyObject_Malloc在4 KiB池中分配小对象(),这种情况很复杂,分配大小为8字节的倍数 - 最多256字节(3.3字节为512字节).池本身在256 KiB竞技场中,因此如果使用一个池中的一个块,则整个256 KiB竞技场将不会被释放.在Python 3.3中,小对象分配器被切换为使用匿名内存映射而不是堆,因此它应该在释放内存时表现更好.

此外,内置类型维护以前分配的对象的空闲列表,这些对象可能使用也可能不使用小对象分配器.该int类型维护一个具有自己分配的内存的空闲列表,清除它需要调用PyInt_ClearFreeList().这可以通过完整来间接调用gc.collect.

试试这样,告诉我你得到了什么.这是psutil的链接.

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)
Run Code Online (Sandbox Code Playgroud)

输出:

Allocation: 3034.36%
Unreference: -752.39%
Collect: -2279.74%
Overall: 2.23%
Run Code Online (Sandbox Code Playgroud)

编辑:

我切换到相对于进程VM大小的测量,以消除系统中其他进程的影响.

当顶部的连续可用空间达到恒定,动态或可配置的阈值时,C运行时(例如glibc,msvcrt)会收缩堆.使用glibc,您可以使用mallopt(M_TRIM_THRESHOLD)进行调整.考虑到这一点,如果堆比你的块减少更多 - 甚至更多 - 也就不足为奇了free.

在3.x range中没有创建列表,因此上面的测试不会创建1000万个int对象.即使它确实如此,int3.x中的类型基本上是2.x long,它不实现空闲列表.

  • 使用 `memory_info()` 而不是 `get_memory_info()` 并且定义了 `x` (2认同)

aba*_*ert 30

eryksun回答了问题#1,我回答了问题#3(原来的#4),但现在让我们回答问题#2:

为什么它特别释放50.5mb - 基于此发布的金额是多少?

它的基础是,最终,Python内部的一系列巧合malloc,很难预测.

首先,根据您测量内存的方式,您可能只测量实际映射到内存的页面.在这种情况下,只要页面被寻呼机换出,内存就会显示为"已释放",即使它尚未被释放.

或者您可能正在测量使用中的页面,这些页面可能会也可能不会计算已分配但从未触摸过的页面(在乐观过度分配的系统上,如linux),已分配但已标记的页面MADV_FREE等.

如果你真的在测量已分配的页面(这实际上不是一件非常有用的事情,但它似乎是你所要求的),并且页面确实被释放了,这可能发生在两种情况:你要么已使用brk或等效缩小数据段(现在非常罕见),或者您已使用munmap或类似地释放映射段.(理论上也是后者的一个小变体,因为有一些方法可以释放映射段的一部分 - 例如,用MAP_FIXED一个MADV_FREE你立即取消映射的段来窃取它.)

但是大多数程序都没有直接从内存页面中分配内容; 他们使用a- mallocstyle分配器.当您调用时free,如果您恰好free是映射中的最后一个活动对象(或数据段的最后N页),则分配器只能将页面释放到操作系统.您的应用程序无法合理地预测这一点,甚至无法提前检测到它.

CPython使这更加复杂 - 它在自定义内存分配器之上有一个自定义的2级对象分配器malloc.(有关更详细的说明,请参阅源代码注释.)最重要的是,即使在C API级别,更少的Python,您甚至无法直接控制何时释放顶级对象.

那么,当你发布一个对象时,你怎么知道它是否会向操作系统释放内存?好吧,首先你必须知道你已经发布了最后一个引用(包括你不知道的任何内部引用),允许GC解除分配.(与其他实现不同,至少CPython会在允许的情况下解除对象的释放.)这通常会在下一级别释放至少两个东西(例如,对于字符串,您释放PyString对象,以及字符串缓冲区) ).

如果你解除分配一个对象,要知道这是否会导致下一级解除分配对象存储的块,你必须知道的对象分配器的内部状态,以及它是如何实现的.(显然不可能发生,除非你要解除块中的最后一件事,即便如此,也可能不会发生.)

如果你确实释放了一个对象存储块,要知道这是否会导致free调用,你必须知道PyMem分配器的内部状态,以及它是如何实现的.(同样,您必须在malloced区域内释放最后一个使用中的块,即使这样,也可能不会发生.)

如果你 free一个malloced区域,要知道这是否会导致一个munmap或同等(或brk),你必须知道它的内部状态malloc,以及它是如何实现的.与其他产品不同,这一产品具有高度的平台特性.(同样,你通常必须解除分段中最后一次使用malloc的问题mmap,即使这样,也可能不会发生.)

所以,如果你想了解为什么它恰好发布了50.5mb,你将不得不从下往上追踪它.malloc当你进行一次或多次free调用时,为什么取消映射50.5mb的页面(可能超过50.5mb)?您必须阅读平台malloc,然后遍历各种表和列表以查看其当前状态.(在某些平台上,它甚至可以使用系统级信息,如果不制作系统快照以进行离线检测,则几乎不可能捕获,但幸运的是,这通常不是问题.)然后你必须在上面的3个级别做同样的事情.

所以,这个问题唯一有用的答案是"因为".

除非您正在进行资源有限(例如,嵌入式)开发,否则您没有理由关注这些细节.

如果你正在做资源有限的发展,了解这些细节是无用的; 你几乎必须围绕所有这些级别进行最终运行,特别mmap是在应用程序级别需要的内存(可能只有一个简单的,易于理解的,特定于应用程序的区域分配器).


de2*_*0ce 6

首先,您可能需要安装一览:

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
Run Code Online (Sandbox Code Playgroud)

然后在终端中运行它!

glances
Run Code Online (Sandbox Code Playgroud)

在 Python 代码中,在文件开头添加以下内容:

import os
import gc # Garbage Collector
Run Code Online (Sandbox Code Playgroud)

使用“Big”变量(例如:myBigVar)后,您想要释放内存,请在 python 代码中编写以下内容:

del myBigVar
gc.collect()
Run Code Online (Sandbox Code Playgroud)

在另一个终端中,运行 python 代码并在“glances”终端中观察系统中的内存是如何管理的!

祝你好运!

PS 我假设您正在 Debian 或 Ubuntu 系统上工作