新聞中心
要精通一門語言,熟悉其內(nèi)容分配和使用機制很重要。對于編譯型語言比如C,C++,內(nèi)存的使用完全由程序員自己代碼分配和管理,所以對C,C++程序員內(nèi)存機制非常熟悉。但是對于動態(tài)語言,比如Python,內(nèi)存在語言層自動管理,所以程序員無需關注太多細節(jié),但是如果要想自己寫的代碼高效可靠,則也必須了解語言的內(nèi)存機制。本文蟲蟲給大家介紹Python語言的內(nèi)存機制,以及如何對其內(nèi)存進行度量。

十余年建站經(jīng)驗, 成都網(wǎng)站設計、網(wǎng)站建設客戶的見證與正確選擇。創(chuàng)新互聯(lián)提供完善的營銷型網(wǎng)頁建站明細報價表。后期開發(fā)更加便捷高效,我們致力于追求更美、更快、更規(guī)范。
概述
考慮以下代碼:
- import numpy as np
- cc= np.ones((1024, 1024, 1024, 3), dtype=np.uint8)
該代碼將會創(chuàng)建一個3GB字節(jié)的數(shù)組,并且都用1來填充。同學們,可能會這樣預想運行該代碼后,進程將會自動分配3GB的內(nèi)存用來使用,事實是不是如此呢?
測量內(nèi)存的一種方法是使用“常駐內(nèi)存”,在Python中可以使用psutil庫工具獲取方便的這些信息,檢查當前進程的常駐內(nèi)存:
- import psutil
- psutil.Process().memory_info().rss /(1024 * 1024)
- 3093
在該示例中,進程使用了3093MB或3.09GB,與數(shù)組大小的無區(qū)別,和預想的一樣。
但是常駐內(nèi)存實際上沒那么簡單。假設在機器上運行一些耗內(nèi)存的任務。然后切換回解釋器,再次運行完全相同的命令:
- psutil.Process().memory_info().rss / (1024 * 1024)
- 2903.12109375
這是怎么回事? 內(nèi)存少了200MB。
為了解釋這個現(xiàn)象,需要了解操作系統(tǒng)如何內(nèi)存管理機制。
簡化模型
當前正運行的程序都會分配一些內(nèi)存,即從操作系統(tǒng)取回虛擬內(nèi)存中的地址。 虛擬內(nèi)存是一個特定于進程的地址空間,本質上是來自0至264-1,進程可以讀取或寫入字節(jié)。
在C語言中,程序員可以使用malloc()或者mmap()函數(shù)進行手動內(nèi)存分配;而在Python中,我們只需創(chuàng)建對象,Python 解釋器將在底層自動調(diào)用malloc()或者mmap()。然后該進程可以讀取或寫入該特定地址和連續(xù)字節(jié)。
Linux下可以用ltrace工具跟蹤調(diào)用malloc(),運行下面Python代碼:
- import numpy as np
- cc = np.ones((170_000,), dtype=np.uint8)
然后可以運行l(wèi)trace:
- ltrace -e malloc python ones.py
- ...
- _multiarray_umath.cpython-39-x86_64-linux-gnu.so->malloc(170000) = 0x5638862a45e0
- ...
整個過程Python 創(chuàng)建一個NumPy數(shù)組。
在Python引擎NumPy調(diào)用malloc()。
這樣做的結果malloc()是內(nèi)存中的地址:0x5638862a45e0。
然后,用于實現(xiàn)NumPy的C代碼可以讀取和寫入該地址和下一個連續(xù)的169,999 個地址,每個地址代表虛擬內(nèi)存中的一個字節(jié)。
這 170,000個字節(jié)存儲在哪里?
它們可以存儲在RAM中;這是默認設置。
它們可以存儲在計算機的硬盤驅動器或磁盤上,即swap分區(qū)交換中。
一些字節(jié)可能存儲在 RAM 中,一些字節(jié)可能存儲在交換分區(qū)中。
常駐內(nèi)存
RAM很快,而硬盤IO很慢,但RAM很貴。通常電腦硬盤驅動器空間比RAM多得多。例如,目前主流的計算機都會有2T左右的硬盤存儲空間,但只會16GB的RAM。
理想情況下,程序的所有內(nèi)存都將存儲在內(nèi)存RAM中,但計算機上運行的各種進程可能分配的內(nèi)存比RAM中可用的內(nèi)存多。如果發(fā)生這種情況,操作系統(tǒng)會將一些數(shù)據(jù)從RAM移動或“交換”到硬盤驅動器。必要時,從交換分區(qū)中獲取數(shù)據(jù),并將未積極使用的數(shù)據(jù)置換進去。
現(xiàn)在我們準備定義我們的第一個內(nèi)存使用量度:常駐內(nèi)存。常駐內(nèi)存是進程分配的內(nèi)存中有多少常駐或存儲在RAM中。
在第一個示例中,首先將所有3GB的已分配數(shù)組存儲在RAM中。
然后,當運行一些任務時,加載這些任務需要分配很多RAM,因此操作系統(tǒng)會將一些數(shù)據(jù)從RAM交換到磁盤交換分區(qū)。結果,Python進程的常駐內(nèi)存下降了:所有數(shù)據(jù)仍然可以訪問,但其中一些已移至磁盤交換分區(qū)。
分配內(nèi)存
測量分配內(nèi)存會很有用,無論操作系統(tǒng)是將數(shù)據(jù)放在RAM中還是將其交換到磁盤,總是3GB內(nèi)存,程序實際需要多少內(nèi)存。
在 Python 中(如果使用的是Linux 或macOS),可以使用Fil memory profiler測量分配的內(nèi)存,它專門測量峰值分配的內(nèi)存。對于之前的示例:
常駐內(nèi)存和分配內(nèi)存之間的權衡
常駐內(nèi)存存在一些問題:
- 內(nèi)存的使用和測量會受到其他進程的影響,由于其他進程可能會爭搶常駐內(nèi)存導致使用的實際使用的RAM會變化。
- 常駐內(nèi)存的上限是可用的物理RAM,所以一旦達到上限,就永遠不會真正了解程序要求多少內(nèi)存。比如主機物理內(nèi)存16GB,對需要17GB內(nèi)存的程序和需要30GB 內(nèi)存的程序,它們駐留內(nèi)存的量都將一致,都將是16GB。
- 另一方面,分配的內(nèi)存不受其他進程的影響,并告訴程序實際請求的內(nèi)容。
當然,常駐內(nèi)存確實比分配內(nèi)存的優(yōu)勢:
- 交換的內(nèi)存很可能永遠不會被使用:想象一下創(chuàng)建一個數(shù)組,忘記刪除引用,然后在程序的其余部分不再實際使用它。
- 更廣泛地說,由于駐留內(nèi)存從操作系統(tǒng)的角度衡量實際使用的內(nèi)存,因此它可以捕獲對分配的內(nèi)存跟蹤不可見的邊緣情況。
讓我們看一個這樣的邊緣情況的例子。
總結
到目前為止示例中,我們一直在分配充滿1的數(shù)組。如果測量已分配的內(nèi)存,則數(shù)組填充的內(nèi)容沒有區(qū)別:可以切換到創(chuàng)建充滿零的數(shù)組,并且仍然得到完全相同的結果。
但是在Linux 上,再看一個例子:
- import numpy as np
- import psutil
- arr = np.zeros((1024, 1024, 1024, 3), dtype=np.uint8)
- psutil.Process().memory_info().rss/(1024 * 1024)
- 28.5546875
這次,還是分配了一個3GB的數(shù)組,但是給數(shù)組的元素都是零。然后測量常駐內(nèi)存——數(shù)組并沒有被計算到,常駐內(nèi)存只有29M。數(shù)組占用的內(nèi)存呢?
事實證明,Linux 不會費心將所有這些零存儲在RAM中。而只是在實際訪問數(shù)據(jù)時向RAM添加零塊,并不會實際分配內(nèi)存。
最后,需要提及的是,我們在說的內(nèi)存使用模型也是理想狀態(tài)的。還沒有包括文件緩存、分配器中的內(nèi)存碎片或其他可用指標等。
話雖如此,對于許多應用程序來說,分配的內(nèi)存可能足以作為幫助優(yōu)化程序內(nèi)存使用的必要措施。
名稱欄目:Python內(nèi)存分配,常駐內(nèi)存和測量
轉載注明:http://www.5511xx.com/article/cddshoh.html


咨詢
建站咨詢
