理解python中的内存使用

2024-04-26 19:09:46 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图理解python是如何使用内存来估计一次可以运行多少个进程。现在,我在一个有大量ram(大约90-150GB的空闲ram)的服务器上处理大文件。在

对于一个测试,我会用python来做一些事情,然后看看htop的用法。在

第一步:打开一个2.55GB的文件并将其保存为字符串

with open(file,'r') as f:
    data=f.read()

使用量为2686M

第2步:我用换行符拆分文件

^{pr2}$

使用量为74.76亿

第三步:我只保留第四行(删除的三行中有两行与我保留的行长度相等)

data=[data[x] for x in range(0,len(data)) if x%4==1]

使用量为85.43亿

第4步:我把它分成20个相等的块来运行一个多处理池。在

l=[] 
for b in range(0,len(data),len(data)/40):
    l.append(data[b:b+(len(data)/40)])

使用量为8621M

第5步:删除数据,使用量为8496M。

有几件事对我来说没有意义。在

在第二步中,为什么当我将字符串更改为数组时,内存使用量会增加这么多。我假设数组容器比字符串容器大得多?在

在第三步中,为什么数据没有显著缩小。实际上,我去掉了3/4的数组和数组中至少2/3的数据。我预计它会相应地缩小。调用垃圾回收器没有任何区别。在

奇怪的是,当我将较小的数组分配给另一个变量时,它使用的内存更少。使用量6605M

当我删除旧对象时data用法6059M

我觉得这很奇怪。如果你能帮我缩小我的记忆足迹,我将不胜感激。在

编辑

好吧,这让我头疼。很明显,python在幕后做了一些奇怪的事情。。。只有Python。我用我的原始方法和下面的答案中建议的方法制作了以下脚本来演示这一点。数字都以GB为单位。在

测试代码

import os,sys
import psutil
process = psutil.Process(os.getpid())
import time

py_usage=process.memory_info().vms / 1000000000.0
in_file = "14982X16.fastq"

def totalsize(o):
    size = 0
    for x in o:
        size += sys.getsizeof(x)
    size += sys.getsizeof(o)
    return "Object size:"+str(size/1000000000.0)

def getlines4(f):
    for i, line in enumerate(f):
        if i % 4 == 1:
            yield line.rstrip()

def method1():
    start=time.time()
    with open(in_file,'rb') as f:
        data = f.read().split("\n")
    data=[data[x] for x in xrange(0,len(data)) if x%4==1]
    return data

def method2():
    start=time.time()
    with open(in_file,'rb') as f:
        data2=list(getlines4(f))
    return data2


print "method1 == method2",method1()==method2()
print "Nothing in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data=method1()
print "data from method1 is in memory"
print "method1", totalsize(data)
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data
print "Nothing in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data2=method2()
print "data from method2 is in memory"
print "method2", totalsize(data2)
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data2
print "Nothing is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage


print "\nPrepare to have your mind blown even more!"
data=method1()
print "Data from method1 is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data2=method2()
print "Data from method1 and method 2 are in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data==data2
print "Compared the two lists"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data
print "Data from method2 is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data2
print "Nothing is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage

输出

method1 == method2 True
Nothing in memory
Usage: 0.001798144
data from method1 is in memory
method1 Object size:1.52604683
Usage: 4.552925184
Nothing in memory
Usage: 0.001798144
data from method2 is in memory
method2 Object size:1.534815518
Usage: 1.56932096
Nothing is in memory
Usage: 0.001798144

Prepare to have your mind blown even more!
Data from method1 is in memory
Usage: 4.552925184
Data from method1 and method 2 are in memory
Usage: 4.692287488
Compared the two lists
Usage: 4.692287488
Data from method2 is in memory
Usage: 4.56169472
Nothing is in memory
Usage: 0.001798144

对于那些使用python3的人来说,它非常相似,除了在比较操作之后没有那么糟糕。。。在

来自PYTHON3的输出

method1 == method2 True
Nothing in memory
Usage: 0.004395008000000006
data from method1 is in memory
method1 Object size:1.718523294
Usage: 5.322555392
Nothing in memory
Usage: 0.004395008000000006
data from method2 is in memory
method2 Object size:1.727291982
Usage: 1.872596992
Nothing is in memory
Usage: 0.004395008000000006

Prepare to have your mind blown even more!
Data from method1 is in memory
Usage: 5.322555392
Data from method1 and method 2 are in memory
Usage: 5.461917696
Compared the two lists
Usage: 5.461917696
Data from method2 is in memory
Usage: 2.747633664
Nothing is in memory
Usage: 0.004395008000000006

故事的寓意。。。python的内存似乎有点像montypython的Camelot…'这是一个非常愚蠢的地方。在


Tags: infrompyinfodataisusageprocess
1条回答
网友
1楼 · 发布于 2024-04-26 19:09:46

我建议你退后一步,以一种直接实现你的目标的方式来处理这个问题:首先减少峰值内存使用。如果一开始就采用注定失败的方法,那么以后再多的分析和修改都无法克服;—)

具体地说,在第一步,通过data=f.read(),你就走错了一步。现在,已经是了,您的程序不可能扩展到一个完全适合RAM的数据文件,并且有足够的空间(运行OS和Python等等)。在

你真的需要所有的数据同时存在于RAM中吗?关于后面的步骤,有太少的细节可以告诉你,但显然在开始时就没有了,因为你马上就想扔掉你读到的75%的行。在

因此,从增量开始:

def getlines4(f):
    for i, line in enumerate(f):
        if i % 4 == 1:
            yield line

即使您只做了那么多,也可以直接跳到步骤3的结果,从而节省了大量的峰值RAM使用:

^{pr2}$

现在,峰值RAM需求与您只关心的行中的字节数成正比,而不是与整个文件字节周期数成正比。在

要继续取得进展,与其一次将所有感兴趣的行具体化,不如将这些行(或行块)以增量的方式提供给您的工作进程。对于我来说,没有足够的细节来建议具体的代码,但是记住这个目标,你就会明白的:你只需要足够的内存来不断地向工作进程提供行,并且可以保存多少需要保留在RAM中的worker进程的结果。不管输入文件大小,峰值内存使用不需要超过“微小”是可能的。在

与内存管理细节抗争要比从一开始就采用内存友好的方法要困难得多。Python本身有几个内存管理子系统,每个子系统都有很多特性。他们又依赖于平台C malloc/free设施,这方面还有很多东西需要学习。我们的仍然与操作系统报告的“内存使用”没有直接关系。平台C库又依赖于平台特定的操作系统内存管理原语,通常只有操作系统内核内存专家才能真正理解这些原语。在

“为什么操作系统说我还在使用N吉布内存?”可以依赖于其中任何一个层中特定于应用程序的细节,甚至依赖于它们之间不幸的或多或少的意外交互。最好先安排不需要问这样的问题。在

编辑-关于CPython的obmalloc

你给出了一些可运行的代码是很好的,但不是很好,除了你之外没有人可以运行它,因为没有其他人拥有你的数据;—)像“有多少行?”以及“线的长度分布是什么?”可能很关键,但我们无法猜测。在

作为现代管理者经常注意到的细节应用之前,我经常会想到具体的细节。它们很复杂,在所有这些水平上的行为都是微妙的。在

Python的主对象分配器(“obmalloc”)从平台C malloc请求“arenas”,块为2*18字节。只要你的应用程序使用的是Python内存系统(这是不可猜测的,因为我们没有你的数据可以处理),256 KiB是从C层请求或返回内存的最小粒度。反过来,C层通常有自己的“分块”策略,这种策略在C实现中各不相同。在

一个Python竞技场依次被划分为4个KiB“池”,每个池都会动态地进行调整,以将每个池划分为固定大小的较小块(8字节块、16字节块、24字节块,…,每个池8*i字节块)。在

只要竞技场中的一个字节用于实时数据,则竞技场必须保留。如果这意味着其他262143个arena字节没有使用,那就倒霉了。正如您的输出所示,所有的内存最终都会返回,那么您为什么真的在意呢?我知道这是一个抽象的有趣的难题,但是如果不努力理解CPython的obmalloc.c中的代码,你是不会解决这个问题的。首先。任何“总结”都会遗漏一个对某些应用程序的微观行为非常重要的细节。在

似是而非:你的字符串足够短,所有字符串对象头和内容(实际字符串数据)的空间都是从CPython的obmalloc获得的。它们会被溅到多个竞技场上。arena可能是这样的,其中“H”表示从中分配字符串对象头的池,“D”表示从中分配字符串数据空间的池:

HHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDD...

在你的method1中,它们会倾向于交替“像那样”,因为创建单个字符串对象需要分别为字符串对象头和字符串对象数据分配空间。当您继续抛出所创建的字符串的3/4时,大约3/4的空间可以重用到Python中。但是一个字节都不能返回到system C,因为仍然有仍然的实时数据散布在整个竞技场,其中包含四分之一的字符串对象没有丢弃(这里“-”表示可供重用的空间):

HHDD      HHDD      HHDD      HHDD  ...

即使你不需要太多的空间,你也不能浪费掉所有的空间。在

为了简单起见;-),我将注意到,关于CPython的obmalloc如何使用的一些细节在Python版本中也有所不同。一般来说,Python版本越新,它就越尝试先使用obmalloc,而不是平台C malloc/free(因为obmalloc通常更快)。在

但是即使您直接使用平台C malloc/free,您仍然可以看到同样的事情发生。内核内存系统调用通常比纯粹在用户空间中运行代码要昂贵得多,因此platformc malloc/free例程通常有自己的策略,“向内核请求比我们单个请求所需的内存多得多的内存,然后自己将其分割成更小的部分”。在

需要注意的是:无论是Python的obmalloc还是platormcmalloc/free实现都无法单独移动实时数据。两者都将内存地址返回给客户端,这些地址无法更改。““洞”是生活中不可避免的事实。在

相关问题 更多 >