用 Python 释放内存

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

  1. 如果我用翻译器,

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

    我机器上使用的真实内存升到了 80.9mb。然后,

    del foo
    

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

  2. 为什么它释放 50.5mb特别是-什么是金额释放的基础上?

  3. 有没有一种方法可以强制 Python 释放所有使用的内存(如果您知道不会再次使用那么多内存的话) ?

注意 这个问题不同于 如何在 Python 中显式释放内存? 因为这个问题主要处理的是即使解释器通过垃圾收集释放了对象(使用或不使用 gc.collect) ,内存使用量也会从基线增加。

183138 次浏览

堆上分配的内存可能会受到高水位标记的影响。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.memory_info().rss


# create approx. 10**7 int objects and pointers
foo = ['abc' for x in range(10**7)]
mem1 = proc.memory_info().rss


# unreference, including x == 9999999
del foo, x
mem2 = proc.memory_info().rss


# collect() calls PyInt_ClearFreeList()
# or use ctypes: pythonapi.PyInt_ClearFreeList()
gc.collect()
mem3 = proc.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,它不实现自由列表。

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

有没有一种方法可以强制 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 的结果,你甚至不会注意到它,但是如果你发送和接收大量的数据,你会想使用一些其他的机制(一个文件,mmapped 或其他; 在 multiprocessing中的共享内存 API; 等等)。
  • 在进程之间发送大量数据意味着数据必须是可腌制的(或者,如果将它们放在文件或共享内存中,则 struct-able 或理想情况下 ctypes-able)。

Eryksun 已经回答了问题1,我也回答了问题3(最初的问题4) ,但现在让我们回答问题2:

为什么它要释放50.5兆特别-释放的数量是基于什么?

它的基础是,最终,Python 和 malloc内部的一系列非常难以预测的巧合。

首先,根据您测量内存的方式,您可能只测量实际映射到内存的页面。在这种情况下,任何时候页面被寻呼机交换出去,内存将显示为“已释放”,即使它还没有被释放。

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

如果你真的在测量已分配的页面(这实际上并不是一件非常有用的事情,但似乎正是你想要的) ,并且页面真的被释放了,有两种情况会发生这种情况: 要么你已经使用了 brk或者相当于缩小了数据段(现在非常罕见) ,要么你已经使用了 munmap或者类似的方法来释放映射段。(理论上,后者还有一个次要的变体,因为有很多方法可以释放映射片段的一部分ーー例如,为了立即取消映射的 MADV_FREE片段,用 MAP_FIXED窃取它。)

但是大多数程序并不直接从内存页中分配内容; 它们使用 malloc风格的分配器。当您调用 free时,如果您恰好是映射(或数据段的最后 N 个页面)中的最后一个活动对象,那么分配器只能将页面释放给操作系统。您的应用程序无法合理地预测这种情况,甚至无法提前检测到它的发生。

CPython 使这种情况更加复杂ーー它在 malloc之上的自定义内存分配器之上有一个自定义2级对象分配器。(详情请参阅 来源评论。)最重要的是,即使在 C API 级别,更不用说 Python 了,您甚至不能直接控制何时释放顶级对象。

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

如果您使用 释放一个对象,要知道这是否会导致下一级释放一个对象存储块,您必须知道对象分配程序的内部状态,以及它是如何实现的。(显然,这种情况不可能发生,除非你释放了块中的最后一个元素,即使那样,它也可能不会发生。)

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

如果你的 free是一个 malloced 区域,要知道这是否会导致 munmap或等效(或 brk) ,你必须知道 malloc的内部状态,以及它是如何实现的。而这一个,与其他的不同,是高度特定于平台的。(同样,您通常必须在 mmap段内释放最后一个在用的 malloc,即使这样,也可能不会发生。)

所以,如果你想知道为什么它恰好释放了50.5 mb,你必须自下而上地追踪它。为什么 malloc取消映射价值50.5 mb 的页面时,你做了那些一个或多个 free调用(可能超过50.5 mb) ?您必须读取平台的 malloc,然后遍历各种表和列表以查看其当前状态。(在某些平台上,它甚至可能使用系统级信息,如果不对系统进行快照以便脱机检查,那么几乎不可能捕获这些信息,但幸运的是,这通常不是一个问题。)然后你必须做同样的事情,在三个水平以上。

所以,这个问题唯一有用的答案就是“因为”

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

如果您使用 进行资源有限的开发,了解这些细节是没有用的; 您几乎必须在所有这些级别上进行最终运行,特别是在应用程序级别上需要的内存 mmap(可能在这两个级别之间使用一个简单的、易于理解的、特定于应用程序的区域分配器)。

首先,你可能需要安装浏览器:

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 代码并在“瞥一眼”终端中观察内存在系统中是如何管理的!

祝你好运!

另外,我猜你正在开发 Debian 或 Ubuntu 系统