簡體   English   中英

了解python中的內存使用情況

[英]Understanding memory usage in python

我試圖了解python如何使用內存來估計我一次可以運行多少個進程。 現在,我在具有大量內存(約90-150GB的可用RAM)的服務器上處理大型文件。

為了進行測試,我將使用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庫又依賴於特定於平台的OS內存管理原語,通常,只有OS內核內存專家才能真正理解它們。

答案為“為什么操作系統說我仍在使用N GiB RAM?” 可能依賴於那些層中任何一層的特定於應用程序的細節,甚至依賴於它們之間或多或少的不幸的偶然交互。 首先最好不要安排這樣的問題。

編輯-關於CPython的obmalloc

您提供了一些可運行的代碼非常棒,但是沒有那么大的優點,因為沒有其他人可以擁有您的數據,因此您只能運行它;-)諸如“有多少行?”之類的東西。 和“線長的分布是什么?” 可能很關鍵,但我們無法猜測。

如前所述,特定於應用程序的細節對於超越現代內存管理器通常是必需的。 它們很復雜, 各個級別的行為可能很微妙。

Python的主要對象分配器(“ obmalloc”)向平台C malloc請求“ arenas”,塊為2 ** 18字節。 只要這是您的應用程序正在使用的Python內存系統(由於我們無法使用您的數據而無法猜測),256 KiB是向或向其請求或返回內存的最小粒度C級。 反過來,C級別通常具有自己的“大塊化”策略,這些策略在C實現中會有所不同。

一個Python競技場又被雕刻成4個KiB“池”,每個池都可以動態地適應成每個池固定大小的較小塊(8字節塊,16字節塊,24字節塊,... ,每個池8 * i字節的塊)。

只要將舞台上的單個字節用於實時數據,就必須保留整個舞台。 如果這意味着其他262,143個競技場字節未使用,那就很不幸。 如您的輸出所示,所有內存最終都返回了,那么您為什么真正關心呢? 我知道這是一個抽象有趣的難題,但是您將obmalloc.c很多精力來理解CPython的obmalloc.c的代碼,而不會解決它。 作為一個開始。 任何“摘要”都將忽略對於某些應用程序的微觀行為實際上非常重要的細節。

合理:您的字符串足夠短,可以從CPython的obmalloc獲得所有字符串對象標頭和內容(實際的字符串數據)的空間。 他們將被散布在多個領域。 競技場可能看起來像這樣,其中“ H”表示從中分配字符串對象頭的池,而“ D”表示從中分配字符串數據空間的池:

HHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDDHHDD...

在您的method1它們傾向於交替使用“ like”,因為創建單個字符串對象需要分別為字符串對象標頭和字符串對象數據分配空間。 當繼續扔掉創建的字符串的3/4分時,該空間的大約3/4分就可以被Python重用。 但是不能將一個字節返回給系統C,因為仍然有整個現場散布的實時數據,其中包含您沒有丟棄的四分之一的字符串對象(這里的“-”表示可重復使用的空間):

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

實際上,有太多的可用空間,即使您沒有丟掉method1結果,浪費較少的method2可能會從method1留下的--------漏洞中獲取所需的所有內存。 。

為了使事情簡單;-),我將注意到有關如何使用CPython的obmalloc的一些細節在不同的Python版本中也有所不同。 通常,Python版本越新,它嘗試使用obmalloc而不是平台C malloc / free的嘗試就越多(因為obmalloc通常更快)。

但是,即使直接使用平台C malloc / free,您仍然可以看到同樣的情況。 內核內存系統調用通常比純粹在用戶空間中運行代碼要昂貴,因此平台C malloc / free例程通常具有自己的策略,“向內核請求的內存要比單個請求所需的內存多得多,並將其分解為我們自己的小塊”。

需要注意的一點:Python的obmalloc和platorm C malloc / free實現都不會自行移動實時數據。 兩者都將內存地址返回給客戶端,而這些地址不能更改。 在這兩種情況下,“孔”都是生活中不可避免的事實。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM