python - 了解python中的内存使用情况

标签 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

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

最佳答案

我将建议您退后一步,以直接解决您的目标的方式来解决这个问题:从一开始就减少峰值内存使用。使用注定的方法开始之后,再多的分析和摆弄都无法克服;-)

具体来说,你在第一步走错了路,通过 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 使用量:
with open(file, 'r') as f:
    data = list(getlines4(f))

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

继续取得进展,而不是在 data 中实现所有感兴趣的线路在一个巨大的吞咽中,也将行(或行的块)以增量方式提供给您的工作进程。我没有足够的细节来为此提出具体的代码,但请记住目标,你会明白的:你只需要足够的 RAM 来不断地向工作进程提供线路,并节省多少您需要将工作进程的结果保存在 RAM 中。无论输入文件大小如何,峰值内存使用量都可能不需要超过“微小”。

相反,解决内存管理细节比开始采用内存友好的方法要困难得多。 Python 本身有几个内存管理子系统,关于它们中的每一个都可以说很多。他们反过来又依赖于平台 C 的 malloc/free 设施,还有很多东西要学习。而且我们还没有达到与您的操作系统报告的“内存使用”直接相关的水平。平台 C 库反过来依赖于特定于平台的操作系统内存管理原语,通常只有操作系统内核内存专家才能真正理解这些原语。

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

编辑 - 关于 CPython 的 obmalloc

很高兴您提供了一些可运行的代码,但还没有好到除了您之外没有人可以运行它,因为没有其他人拥有您的数据;-) 诸如“有多少行?”之类的事情。和“线长的分布是什么?”可能很关键,但我们无法猜测。

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

Python 的主要对象分配器(“obmalloc”)从平台 C malloc 请求“arenas”,2**18 字节的块。只要这是您的应用程序正在使用的 Python 内存系统(无法猜测,因为我们没有您的数据可以使用),256 KiB 是请求或返回内存的最小粒度, C级。 C 级别通常有自己的“分块”策略,这些策略因 C 实现而异。

Python arena 又被划分为 4 KiB “池”,每个池动态适应被划分为每个池固定大小的较小块(8 字节块、16 字节块、24 字节块,...... , 每个池 8*i 字节块)。

只要 arena 中的单个字节用于实时数据,就必须保留整个 arena。如果这意味着其他 262,143 个 arena 字节未使用,那么运气不好。正如你的输出所示,所有的内存最后都返回了,那你为什么真的在乎呢?我知道这是一个抽象有趣的谜题,但如果不付出大量努力来理解 CPython obmalloc.c 中的代码,你就无法解决它。 .作为一个开始。任何“摘要”都会遗漏对某些应用程序的微观行为实际上很重要的细节。

合理的:你的字符串足够短,所有字符串对象头和内容(实际的字符串数据)的空间都是从 CPython 的 obmalloc 获得的。它们将被喷洒在多个舞台上。竞技场可能看起来像这样,其中“H”代表从中分配字符串对象 header 的池,而“D”代表从中分配字符串数据空间的池:
HHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDD...

在您的 method1他们倾向于“那样”交替,因为创建单个字符串对象需要为字符串对象 header 和字符串对象数据分别分配空间。当您继续丢弃您创建的字符串的 3/4 时,该空间的或多或少 3/4 可重用于 Python。但是没有一个字节可以返回到系统 C,因为仍然有实时数据散布在整个舞台上,其中包含您没有扔掉的字符串对象的四分之一(这里的“-”表示可重复使用的空间):
HHDD------------HHDD------------HHDD------------HHDD----...

有如此多的可用空间,事实上,浪费更少的空间是可能的method2可以从--------获得它需要的所有内存method1遗留的孔即使你不扔掉 method1结果。

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

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

需要注意的一点:Python 的 obmalloc 和平台 C malloc/free 实现都不会自行移动实时数据。两者都将内存地址返回给客户端,并且这些地址不能更改。 “洞”是两者下不可回避的生活事实。

关于python - 了解python中的内存使用情况,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50124621/

相关文章:

c++ - 如何释放C++类中静态指针指向的内存

python - 仅计算 Pandas 数据系列中的当前行和前一行

python - 从列表中的分数元组分配排名

python - 使用带字节数的 textwrap.wrap

java - Java 中的性能/内存用于在多个文件上生成数据

c# - 为什么 .NET 为我的应用程序保留这么多内存?

c++ - Cout 不打印指向内存的内容

python - 博托EC2 : Create an instance with tags

python - jupyter笔记本中的seaborn : why does sns. despine() 适用于 lmplot 但不适用于 regplot?

python - 为什么 lxml.etree.iterparse() 会占用我所有的内存?