在 Python 中释放内存

2024-12-13 08:36:00
admin
原创
136
摘要:问题描述:我对以下示例中的内存使用情况有几个相关问题。如果我在解释器中运行,foo = ['bar' for _ in xrange(10000000)] 我的机器上实际使用的内存上升到80.9mb。然后我del foo 实际内存会减少,但只会减少到30.4mb。解释器使用基线,那么不向操作系统4.4mb释放...

问题描述:

我对以下示例中的内存使用情况有几个相关问题。

  1. 如果我在解释器中运行,

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

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

del foo

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

  1. 为什么要50.5mb特别释放,释放的量是根据什么?

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

注意:
这个问题与如何在 Python 中明确释放内存?不同
,因为这个问题主要涉及即使在解释器通过垃圾收集释放对象(无论是否使用)之后,内存使用量仍然从基线增加gc.collect


解决方案 1:

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

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

没有。但有一个简单的解决方法:子进程。

如果您需要 500MB 的临时存储空间,但运行 5 分钟后又需要运行 2 个小时,并且不会再使用那么多内存,则可生成一个子进程来执行内存密集型工作。当子进程消失时,内存就会被释放。

这并不是完全简单和免费的,但它相当容易和便宜,这通常足以使交易值得。

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

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

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

费用如下:

  • 在某些平台上,尤其是 Windows,进程启动速度有点慢。我们这里说的是几毫秒,而不是几分钟,如果你启动一个子进程来完成 300 秒的工作,你甚至不会注意到这一点。但这不是免费的。

  • 如果您使用的临时内存量确实很大那么这样做可能会导致您的主程序被换出。当然,从长远来看,您可以节省时间,因为如果该内存永远挂在那里,那么它必须在某个时候导致交换。但在某些用例中,这可能会将逐渐缓慢的速度转变为非常明显的一次性(和早期)延迟。

  • 在进程之间发送大量数据可能会很慢。同样,如果您要发送超过 2K 的参数并返回 64K 的结果,您甚至不会注意到这一点,但是如果您要发送和接收大量数据,您将需要使用其他机制(文件、mmapped 或其他;中的共享内存 API multiprocessing;等等)。

  • 在进程之间发送大量数据意味着数据必须是可腌制的(或者,如果将它们粘贴在文件或共享内存中,则是struct可腌制的,或者理想情况下是ctypes可腌制的)。

解决方案 2:

堆上分配的内存可能会受到高水位线的影响。由于 Python 对PyObject_Malloc在 4 KiB 池中分配小对象()的内部优化,这个问题变得复杂,分配大小为 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对象。即使创建了,int3.x 中的类型基本上是 2.x 中的类型long,它没有实现空闲列表。

解决方案 3:

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

为什么它特别释放了50.5mb——这个释放量是基于什么而来的?

归根结底,它所基于的是 Python 内部的一系列巧合,而且malloc很难预测。

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

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

如果您确实在测量已分配的页面(这实际上不是一件非常有用的事情,但这似乎是您要问的),并且页面确实已被释放,则可能发生这种情况的两种情况:您已经使用或等效于brk缩小数据段(现在非常罕见),或者您已经使用munmap或类似来释放映射段。 (理论上后者还有一个小变体,即有方法可以释放部分映射段 - 例如,将其窃取MAP_FIXEDMADV_FREE您立即取消映射的段。)

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

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

那么,当您释放一个对象时,您如何知道它是否会将内存释放给操作系统?好吧,首先您必须知道您已经释放了最后一个引用(包括您不知道的任何内部引用),从而允许 GC 释放它。(与其他实现不同,至少 CPython 会在允许时立即释放对象。)这通常会在下一级释放至少两个东西(例如,对于字符串,您正在释放对象PyString和字符串缓冲区)。

如果您确实释放了一个对象,要知道这是否会导致下一级释放一个对象存储块,您必须了解对象分配器的内部状态以及它的实现方式。(除非您释放块中的最后一个对象,否则这显然不可能发生,即使这样,也可能不会发生。)

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

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

因此,如果您想了解为什么恰好释放了 50.5mb,您必须从下往上进行跟踪。为什么malloc在执行一个或多个free调用时取消映射了价值 50.5mb 的页面(可能比 50.5mb 多一点)?您必须阅读平台的malloc,然后遍历各种表和列表以查看其当前状态。(在某些平台上,它甚至可能利用系统级信息,如果不对系统进行快照以进行离线检查,则几乎不可能捕获这些信息,但幸运的是,这通常不是问题。)然后您必须在高于它的 3 个级别上执行相同的操作。

因此,这个问题唯一有用的答案是“因为”。

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

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

解决方案 4:

首先,您可能需要安装 Glances:

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 系统

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用