了解python中的内存使用情况

时间:2018-05-01 22:16:41

标签: python memory

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

对于测试,我会在python中执行操作,然后查看htop以查看用法是什么。

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

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

用法为2686M

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

data = data.split('\n')

使用量为7476M

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

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

使用量为8543M

第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
故事的寓意......对于蟒蛇的记忆似乎有点像Monty Python的Camelot ......这是一个非常愚蠢的地方。

1 个答案:

答案 0 :(得分:5)

我建议您退出并以直接解决目标的方式处理此问题:缩短峰值内存使用量。没有多少分析和稍后摆弄可以克服使用注定的方法开始; - )

具体地说,你在第一步,通过data=f.read()走错了路。现在它已经你的程序可能无法扩展到完全适合RAM的数据文件,而且还有空间(运行操作系统和Python ......)也是。

您是否确实需要所有数据一次在RAM中?关于后续步骤的细节太少,但显然不是在开始时,因为你立即想要丢掉你读过的75%的行。

因此,首先要逐步执行此操作:

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

即使您没有做任何其他事情,您也可以直接跳到步骤3的结果,节省大量的峰值RAM使用:

with open(file, 'r') as f:
    data = list(getlines4(f))

现在,峰值RAM需求与您关注的唯一行中的字节数成比例,而不是与文件字节周期的总数相关。

要继续取得进步,而不是在一个巨大的吞咽中实现data中的所有感兴趣的行,而是将行(或行的行)递增地提供给您的工作进程。我没有足够详细的建议为此提出具体的代码,但要记住目标并且你要弄明白:你只需需要足够的RAM来保持增量输入线工人流程,以及拯救大部分工人流程'结果你需要保留在RAM中。 可能无论输入文件大小如何,峰值内存使用都不需要超过" tiny&#34 ;.

战胜内存管理细节要比开始使用内存友好的方法困难得多。 Python本身有几个内存管理子系统,关于每个子系统都可以说很多。他们反过来依赖于平台C malloc /免费设施,这也是很有待学习的。而且我们不在与操作系统报告的内容直接相关的级别"内存使用"。平台C库反过来依赖于特定于平台的操作系统内存管理原语,这些原语通常只有操作系统内核内存专家才能真正理解。

答案为"操作系统为什么说我还在使用N GiB的RAM?"可以依赖于这些层中的任何一个层中的特定于应用程序的细节,甚至可以依赖于它们之间偶然的或多或少的偶然交互。最好安排不要开始提出这样的问题。

编辑 - 关于CPython&#s; obmalloc

很高兴您提供了一些可运行的代码,但不是很好,以至于没有人可以运行它,因为没有其他人拥有您的数据;-)类似于"有多少行?&# 34;和"线路长度的分布是什么?"可能很关键,但我们无法猜测。

正如我之前提到的,通常需要特定于应用程序的细节来超越现代内存管理器。它们很复杂,所有级别的行为都很微妙。

Python的主要对象分配器(&#34; obmalloc&#34;)请求&#34;竞技场&#34;从平台C malloc,2 ** 18字节的块。只要这是您的应用程序使用的Python内存系统(由于我们没有使用您的数据而无法猜到),256 KiB是最小< / em>从C级别请求或返回内存的粒度。反过来,C级通常会有很多东西。它自己的策略,在C实现中各不相同。

Python竞技场反过来分为4个KiB&#34;游泳池&#34;,每个游戏动态适应被雕刻成每个游泳池固定大小的小块(8字节块,16字节块, 24个字节的块,...,每个池8 *个i字节块。

只要竞技场中的单个字节用于实时数据,就必须保留整个竞技场。如果这意味着其他262,143个竞技场字节未使用,那就太难了。如您的输出所示,最后返回所有内存,那么为什么你真的关心?我理解这是一个抽象有趣的谜题,但是你不会在花费大量精力来理解CPython obmalloc.c中的代码时解决它。作为一个开始。任何&#34;摘要&#34;会遗漏一些对某些应用程序的微观行为实际上重要的细节。

似是而非:你的字符串足够短,所有字符串对象标题和内容(实际字符串数据)的空间都是从CPython的obmalloc获得的。它们将在多个竞技场上肆虐。竞技场可能看起来像这样,&#34; H&#34;表示分配了字符串对象标题的池,&#34; D&#34;从中分配字符串数据空间的池:

HHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDD...

method1中,他们会倾向于交替使用&#34;就像那样&#34;因为创建单个字符串对象需要为字符串对象标题和字符串对象数据分别分配空间。当你继续抛出你创建的3/4的字符串时,那个空间的3/4或多或少可以重复使用到Python 。但是没有一个字节可以返回到系统C,因为它的仍然现场数据喷洒在整个竞技场上,包含你没有扔掉的四分之一的字符串对象(这里) &#34; - &#34;表示可以重复使用的空间):

HHDD------------HHDD------------HHDD------------HHDD----...

有这么多的自由空间,事实上,浪费较少的method2可以从--------留下的method1洞获得所需的全部内存。 method1,即使您没有丢弃{{1}}结果。

为了简单起见;-),我注意到有关CPython的obmalloc如何被使用的一些细节也因Python版本而异。一般来说,Python发布越近,尝试首先使用obmalloc而不是平台C malloc / free(因为obmalloc通常更快)。

但即使你直接使用C malloc / free平台,你仍然可以看到同样的事情发生。内核内存系统调用通常比纯粹在用户空间中运行代码更昂贵,因此平台C malloc / free例程通常有自己的策略,要求内核提供比单个请求所需的更多内存,并且雕刻它自己变成了更小的部分&#34;。

需要注意的事项:Python的obmalloc和platorm C malloc / free实现都不会自己移动实时数据。两者都将内存地址返回给客户端,而这些地址无法更改。 &#34;孔&#34;两者都是不可避免的生活现实。