簡體   English   中英

如何在 python 腳本中跟蹤內存使用增長 object

[英]How to track a memory-usage growing object in a python script

我有一個 python 腳本,其 memory 使用量(在top觀察)在腳本運行時無限增長。

我的腳本不應該存儲任何東西。 它只是接收一些請求(REST API),處理請求並返回一些結果。 預期的行為是 memory 的使用保持不變。

我正在尋找合適的 memory 分析器工具,它可以讓我確定不斷增長的 object 在代碼中的位置。

我查看了FIL-profiler ,但它似乎旨在考慮“峰值 memory 使用”,我相信這不是我的情況。

謝謝

EDIT1:我想嘗試一下pyrasite ,但我無法讓它發揮作用。 由於本項目最后一次更新是2012年5月,可能是不再兼容當前的python(我用的是3.8)

EDIT2:在這篇文章之后,我嘗試了Pimpler 我在腳本中添加了以下代碼:

from pympler import muppy, summary
all_objects = muppy.get_objects()
sum = summary.summarize(all_objects)
summary.print_(sum)

這輸出:

    types |   # objects |   total size
========= | =========== | ============
     dict |        7628 |      3.15 MB
      str |       24185 |      2.71 MB
     type |        1649 |      1.39 MB
    tuple |       13722 |      1.35 MB
     code |        7701 |      1.06 MB
      set |         398 |    453.06 KB
....

其中沒有顯示任何可疑的大 object (腳本在運行時累積 memory 使用量達到 GB 規模)

EDIT3:我嘗試使用tracemalloc :我在腳本的開頭放置了一個tracemalloc.start() ,然后在腳本的末尾,但在停止之前(這樣 memory 的使用率顯然非常高,根據top ),我做一個

snapshot = tracemalloc.take_snapshot()
display_top(snapshot)

這將顯示 memory 使用率最高的代碼行。 但是,與在top中觀察到的相比,仍然沒有什么。 我也嘗試了 gc.collect(),但這沒有效果。

我無法利用任何建議的 memory 分析器。 原來問題出在庫ujson中可能存在的錯誤,該庫是用 C 編寫的。 我想這就是為什么所有那些 python memory 分析器在這里都幫不上忙的原因。 我想我必須關閉這個問題。 剩下的問題是:是否有任何 python 工具可以跟蹤 C 模塊中發生的 memory 問題?

假設您使用的是頂部的“VIRT”列,您無法從該數字推斷您的 python 對象的數量正在增長,或者至少增長到足以解釋虛擬地址空間的總大小。

例如,您知道 python 在其線程代碼下使用 pthreads 嗎? 這很重要,因為 pthreads 將“ulmimit -s”* 1K 的值作為默認堆棧大小。 因此,在 Linux 的某些變體上,python 中任何新線程的默認堆棧大小通常為 8MB 甚至 40MB,除非您顯式更改父進程中的“ulimit -s”值。 許多服務器應用程序是多線程的,因此即使有 2 個額外的線程也會導致比您從 Pympler output 中顯示的更多的虛擬 memory。 您必須知道進程中有多少線程以及默認堆棧大小是多少,才能了解線程對總 VIRT 值的影響。

此外,在其自己的分配機制下,python 使用 mmap 和 malloc 的混合。 In the case of malloc, if the program is multi-threaded, on linux libc malloc will use multiple arenas, which reserve 64MB ranges (called heaps) at a time, but only make the starts of these heaps readable and writable and leave the tails在需要 memory 之前無法訪問這些范圍。 那些不可訪問的尾部通常很大,但就進程的提交 memory 而言根本不重要,因為不可訪問的頁面不需要任何物理 memory 或交換空間。 盡管如此,“VIRT”中的頂部計數整個范圍,包括范圍開頭的可訪問開始和范圍末尾的不可訪問開始。

例如,考慮相當小的 python 程序,其中主線程啟動了 16 個附加線程,每個線程在 python 分配中使用的 memory 並不多:

import threading

def spin(seed):
    l = [ i * seed for i in range(64) ]
    while True:
       l = [ i * i % 97 for i in l ]

for i in range(16):
    t = threading.Thread(target=spin, args=[i])
    t.start()

我們不希望該程序產生這么大的進程,但這是我們在頂部看到的,僅查看一個進程,它顯示了超過 1GB 的 VIRT:

Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s):  1.0%us,  1.9%sy,  3.3%ni, 93.3%id,  0.5%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  264401648k total, 250450412k used, 13951236k free,  1326116k buffers
Swap: 69205496k total, 17321340k used, 51884156k free, 104421676k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                      
 9144 tim       20   0 1787m 105m  99m S 101.8  0.0  13:40.49 python                                                                                                                                       

我們可以通過獲取正在運行的程序的實時核心(例如使用 gcore)並使用開源 chap 打開生成的核心來了解此類程序(以及您的程序)的高 VIRT 值,可以在https://github.com/vmware/chap和運行命令如圖:

chap> summarize writable
17 ranges take 0x2801c000 bytes for use: stack
14 ranges take 0x380000 bytes for use: python arena
16 ranges take 0x210000 bytes for use: libc malloc heap
1 ranges take 0x1a5000 bytes for use: libc malloc main arena pages
11 ranges take 0xa9000 bytes for use: used by module
1 ranges take 0x31000 bytes for use: libc malloc mmapped allocation
2 ranges take 0x7000 bytes for use: unknown
62 writable ranges use 0x28832000 (679,682,048) bytes.

從上面我們可以看出memory的最大單次使用是17棧。 如果我們使用“describe writable”,我們會看到其中 16 個堆棧每個占用 40MB,這都是因為我們忽略了將堆棧大小顯式調整為更合理的值。

我們可以對不可訪問(不可讀、不可寫或可執行)區域執行類似的操作,並看到 16 個“libc malloc 堆尾保留”范圍占用了將近 1GB 的 VIRT。 事實證明,這對於該過程的已提交 memory 確實很重要,但它確實對 VIRT 數量做出了相當可怕的貢獻。

chap> summarize inaccessible
16 ranges take 0x3fdf0000 bytes for use: libc malloc heap tail reservation
11 ranges take 0x15f9000 bytes for use: module alignment gap
16 ranges take 0x10000 bytes for use: stack overflow guard
43 inaccessible ranges use 0x413f9000 (1,094,684,672) bytes.

有 16 個這樣的范圍的原因是每個旋轉線程都在重復進行分配,這導致 libc malloc 在幕后運行,因為它被 python 分配器使用,從而划分出 16 個競技場。

您可以對只讀 memory(“summarize readonly”)執行類似的命令,或者您可以通過將“summarize”更改為“describe”來獲取任何命令的更多詳細信息。

我不能確切地說當你在自己的服務器上運行它時你會發現什么,但 REST 服務器似乎很可能是多線程的,所以我猜這將是對數字的重要貢獻TOP向您展示。

這仍然不能回答為什么這個數字會繼續攀升,但是如果您查看這些數字,您可以看到下一步該往哪里看。 例如,在上面由 python 處理而不使用 mmap 的摘要分配中,memory 僅占用 3.5MB:

14 ranges take 0x380000 bytes for use: python arena

在您的情況下,數字可能更大,因此您可以嘗試以下任何一種方法:

describe used
describe free
summarize used
summarize free

請注意,上述命令還將涵蓋本機分配(例如由共享庫完成的分配)以及 python 分配。

在您自己的程序中執行類似的步驟應該可以讓您更好地理解您所看到的那些“頂級”數字。

暫無
暫無

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

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