
作者:豌豆花下貓
來源:Python貓
我之前的一篇文章,帶大家揭曉了python 在給內(nèi)置對(duì)象分配內(nèi)存時(shí)的 5 個(gè)奇怪而有趣的小秘密。文中使用了sys.getsizeof()來計(jì)算內(nèi)存,但是用這個(gè)方法計(jì)算時(shí),可能會(huì)出現(xiàn)意料不到的問題。
文檔中關(guān)于這個(gè)方法的介紹有兩層意思:
也就是說,getsizeof() 并不是計(jì)算實(shí)際對(duì)象的字節(jié)大小,而是計(jì)算“占位對(duì)象”的大小。如果你想計(jì)算所有屬性以及屬性的屬性的大小,getsizeof() 只會(huì)停留在第一層,這對(duì)于存在引用的對(duì)象,計(jì)算時(shí)就不準(zhǔn)確。
例如列表 [1,2],getsizeof() 不會(huì)把列表內(nèi)兩個(gè)元素的實(shí)際大小算上,而只是計(jì)算了對(duì)它們的引用。舉一個(gè)形象的例子,我們把列表想象成一個(gè)箱子,把它存儲(chǔ)的對(duì)象想象成一個(gè)個(gè)球,現(xiàn)在箱子里有兩張紙條,寫上了球 1 和球 2 的地址(球不在箱子里),getsizeof() 只是把整個(gè)箱子稱重(含紙條),而沒有根據(jù)紙條上地址,找到兩個(gè)球一起稱重。
1、計(jì)算的是什么?
我們先來看看列表對(duì)象的情況:
如圖所示,單獨(dú)計(jì)算 a 和 b 列表的結(jié)果是 36 和 48,然后把它們作為 c 列表的子元素時(shí),該列表的計(jì)算結(jié)果卻僅僅才 36。(PS:我用的是 32 位解釋器)
如果不使用引用方式,而是直接把子列表寫進(jìn)去,例如 “d = [[1,2],[1,2,3,4,5]]”,這樣計(jì)算 d 列表的結(jié)果也還是 36,因?yàn)樽恿斜硎仟?dú)立的對(duì)象,在 d 列表中存儲(chǔ)的是它們的 id。
也就是說:getsizeof() 方法在計(jì)算列表大小時(shí),其結(jié)果跟元素個(gè)數(shù)相關(guān),但跟元素本身的大小無關(guān)。
下面再看看字典的例子:
明顯可以看出,三個(gè)字典實(shí)際占用的全部?jī)?nèi)存不可能相等,但是 getsizeof() 方法給出的結(jié)果卻相同,這意味著它只關(guān)心鍵的數(shù)量,而不關(guān)心實(shí)際的鍵值對(duì)是什么內(nèi)容,情況跟列表相似。
2、“淺計(jì)算”與其它問題
有個(gè)概念叫“淺拷貝”,指的是 copy() 方法只拷貝引用對(duì)象的內(nèi)存地址,而非實(shí)際的引用對(duì)象。類比于這個(gè)概念,我們可以認(rèn)為 getsizeof() 是一種“淺計(jì)算”。
“淺計(jì)算”不關(guān)心真實(shí)的對(duì)象,所以其計(jì)算結(jié)果只是一個(gè)假象。這是一個(gè)值得注意的問題,但是注意到這點(diǎn)還不夠,我們還可以發(fā)散地思考如下的問題:
關(guān)于第一個(gè)問題,getsizeof(x) 方法實(shí)際會(huì)調(diào)用 x 對(duì)象的__sizeof__() 魔術(shù)方法,對(duì)于內(nèi)置對(duì)象來說,這個(gè)方法是通過 CPython 解釋器實(shí)現(xiàn)的。
我查到這篇文章《Python中對(duì)象的內(nèi)存使用(一)》,它分析了 CPython 源碼,最終定位到的核心代碼是這一段:
我看不懂這段代碼,但是可以知道的是,它在計(jì)算 Python 對(duì)象的大小時(shí),只跟該對(duì)象的結(jié)構(gòu)體的屬性相關(guān),而沒有進(jìn)一步作“深度計(jì)算”。
對(duì)于 CPython 的這種實(shí)現(xiàn),我們可以注意到兩個(gè)層面上的區(qū)別:
由此,我有一個(gè)不成熟的猜測(cè):基于“一切皆是對(duì)象”的設(shè)計(jì)原則,int 及其它基礎(chǔ)的 C 數(shù)據(jù)類型在 Python 中被套上了一層“殼”,所以需要一個(gè)方法來計(jì)算它們的大小,也即是 getsizeof()。
官方文檔中說“All built-in objects will return correct results” [1],指的應(yīng)該是數(shù)字、字符串和布爾值之類的簡(jiǎn)單對(duì)象。但是不包括列表、元組和字典等在內(nèi)部存在引用關(guān)系的類型。
為什么不推廣到所有內(nèi)置類型上呢?我未查到這方面的解釋,若有知情的同學(xué),煩請(qǐng)告知。
3、“深計(jì)算”與其它問題
與“淺計(jì)算”相對(duì)應(yīng),我們可以定義出一種“深計(jì)算”。對(duì)于前面的兩個(gè)例子,“深計(jì)算”應(yīng)該遍歷每個(gè)內(nèi)部元素以及可能的子元素,累加計(jì)算它們的字節(jié),最后算出總的內(nèi)存大小。
那么,我們應(yīng)該注意的問題有:
Stackoverflow 網(wǎng)站上有個(gè)年代久遠(yuǎn)的問題“How do I determine the size of an object in Python?” [2],實(shí)際上問的就是如何實(shí)現(xiàn)“深計(jì)算”的問題。
有不同的開發(fā)者貢獻(xiàn)了兩個(gè)項(xiàng)目:pympler 和 pysize :第一個(gè)項(xiàng)目已發(fā)布在 Pypi 上,可以“pip install pympler”安裝;第二個(gè)項(xiàng)目爛尾了,作者也沒發(fā)布到 Pypi 上(注:Pypi 上已有個(gè) pysize 庫(kù),是用來做格式轉(zhuǎn)化的,不要混淆),但是可以在 Github 上獲取到其源碼。
對(duì)于前面的兩個(gè)例子,我們可以拿這兩個(gè)項(xiàng)目分別測(cè)試一下:
單看數(shù)值的話,pympler 似乎確實(shí)比 getsizeof() 合理多了。再看看 pysize,直接看測(cè)試結(jié)果是(獲取其源碼過程略):
可以看出,它比 pympler 計(jì)算的結(jié)果略小。就兩個(gè)項(xiàng)目的完整度、使用量與社區(qū)貢獻(xiàn)者規(guī)模來看,pympler 的結(jié)果似乎更為可信。
那么,它們分別是怎么實(shí)現(xiàn)的呢?那微小的差異是怎么導(dǎo)致的?從它們的實(shí)現(xiàn)方案中,我們可以學(xué)習(xí)到什么呢?
pysize 項(xiàng)目很簡(jiǎn)單,只有一個(gè)核心方法:
除去判斷__dict__和 __slots__屬性的部分(針對(duì)類對(duì)象),它主要是對(duì)字典類型及可迭代對(duì)象(除字符串、bytes、bytearray)作遞歸的計(jì)算,邏輯并不復(fù)雜。
以 [1,2] 這個(gè)列表為例,它先用 sys.getsizeof() 算出 36 字節(jié),再計(jì)算內(nèi)部的兩個(gè)元素得 14*2=28 字節(jié),最后相加得到 64 字節(jié)。
相比之下,pympler 所考慮的內(nèi)容要多很多,入口在這:
它可以接受多個(gè)參數(shù),再用 sum() 方法合并。所以核心的計(jì)算方法其實(shí)是 _sizer()。但代碼很復(fù)雜,繞來繞去像一座迷宮:
它的核心邏輯是把每個(gè)對(duì)象的 size 分為兩部分:flat size 和 item size。計(jì)算 flat size 的邏輯在:
這里出現(xiàn)的 mask 是為了作字節(jié)對(duì)齊,默認(rèn)值是 7,該計(jì)算公式表示按 8 個(gè)字節(jié)對(duì)齊。對(duì)于 [1,2] 列表,會(huì)算出 (36+7)&~7=40 字節(jié)。同理,對(duì)于單個(gè)的 item,比如列表中的數(shù)字 1,sys.getsizeof(1) 等于 14,而 pympler 會(huì)算成對(duì)齊的數(shù)值 16,所以匯總起來是 40+16+16=72 字節(jié)。這就解釋了為什么 pympler 算的結(jié)果比 pysize 大。
字節(jié)對(duì)齊一般由具體的編譯器實(shí)現(xiàn),而且不同的編譯器還會(huì)有不同的策略,理論上 Python 不應(yīng)關(guān)心這么底層的細(xì)節(jié),內(nèi)置的 getsizeof() 方法就沒有考慮字節(jié)對(duì)齊。
在不考慮其它 edge cases 的情況下,可以認(rèn)為 pympler 是在 getsizeof() 的基礎(chǔ)上,既考慮了遍歷取引用對(duì)象的 size,又考慮到了實(shí)際存儲(chǔ)時(shí)的字節(jié)對(duì)齊問題,所以它會(huì)顯得更加貼近現(xiàn)實(shí)。
4、小結(jié)
getsizeof() 方法的問題是顯而易見的,我創(chuàng)造了一個(gè)“淺計(jì)算”概念給它。這個(gè)概念借鑒自 copy() 方法的“淺拷貝”,同時(shí)對(duì)應(yīng)于 deepcopy() “深拷貝”,我們還能推理出一個(gè)“深計(jì)算”。
前面展示了兩個(gè)試圖實(shí)現(xiàn)“深計(jì)算”的項(xiàng)目(pysize+pympler),兩者在淺計(jì)算的基礎(chǔ)上,深入地求解引用對(duì)象的大小。pympler 項(xiàng)目的完整度較高,代碼中有很多細(xì)節(jié)上的設(shè)計(jì),比如字節(jié)對(duì)齊。
Python 官方團(tuán)隊(duì)當(dāng)然也知道 getsizeof() 方法的局限性,他們甚至在文檔中加了一個(gè)鏈接 [3],指向了一份實(shí)現(xiàn)深計(jì)算的示例代碼。那份代碼比 pysize 還要簡(jiǎn)單(沒有考慮類對(duì)象的情況)。
未來 Python 中是否會(huì)出現(xiàn)深計(jì)算的方法,假設(shè)命名為 getdeepsizeof() 呢?這不得而知了。
本文的目的是加深對(duì) getsizeof() 方法的理解,區(qū)分淺計(jì)算與深計(jì)算,分析兩個(gè)深計(jì)算項(xiàng)目的實(shí)現(xiàn)思路,指出幾個(gè)值得注意的問題。
讀完這里,希望你也能有所收獲。若有什么想法,歡迎一起交流。
想從事業(yè)務(wù)型數(shù)據(jù)分析師,您可以點(diǎn)擊>>>“數(shù)據(jù)分析師”了解課程詳情;
想從事大數(shù)據(jù)分析師,您可以點(diǎn)擊>>>“大數(shù)據(jù)就業(yè)”了解課程詳情;
想成為人工智能工程師,您可以點(diǎn)擊>>>“人工智能就業(yè)”了解課程詳情;
想了解Python數(shù)據(jù)分析,您可以點(diǎn)擊>>>“Python數(shù)據(jù)分析師”了解課程詳情;
想咨詢互聯(lián)網(wǎng)運(yùn)營(yíng),你可以點(diǎn)擊>>>“互聯(lián)網(wǎng)運(yùn)營(yíng)就業(yè)班”了解課程詳情;
數(shù)據(jù)分析咨詢請(qǐng)掃描二維碼
若不方便掃碼,搜微信號(hào):CDAshujufenxi
LSTM 模型輸入長(zhǎng)度選擇技巧:提升序列建模效能的關(guān)鍵? 在循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)家族中,長(zhǎng)短期記憶網(wǎng)絡(luò)(LSTM)憑借其解決長(zhǎng)序列 ...
2025-07-11CDA 數(shù)據(jù)分析師報(bào)考條件詳解與準(zhǔn)備指南? ? 在數(shù)據(jù)驅(qū)動(dòng)決策的時(shí)代浪潮下,CDA 數(shù)據(jù)分析師認(rèn)證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計(jì)的實(shí)用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強(qiáng)大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠(chéng)摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實(shí)施重大更新。 此次更新旨在確保認(rèn) ...
2025-07-10BI 大數(shù)據(jù)分析師:連接數(shù)據(jù)與業(yè)務(wù)的價(jià)值轉(zhuǎn)化者? ? 在大數(shù)據(jù)與商業(yè)智能(Business Intelligence,簡(jiǎn)稱 BI)深度融合的時(shí)代,BI ...
2025-07-10SQL 在預(yù)測(cè)分析中的應(yīng)用:從數(shù)據(jù)查詢到趨勢(shì)預(yù)判? ? 在數(shù)據(jù)驅(qū)動(dòng)決策的時(shí)代,預(yù)測(cè)分析作為挖掘數(shù)據(jù)潛在價(jià)值的核心手段,正被廣泛 ...
2025-07-10數(shù)據(jù)查詢結(jié)束后:分析師的收尾工作與價(jià)值深化? ? 在數(shù)據(jù)分析的全流程中,“query end”(查詢結(jié)束)并非工作的終點(diǎn),而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報(bào)考到取證的全攻略? 在數(shù)字經(jīng)濟(jì)蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭(zhēng)搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢(shì)性檢驗(yàn):捕捉數(shù)據(jù)背后的時(shí)間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢(shì)性檢驗(yàn)如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時(shí)間維度的精準(zhǔn)切片? ? 在數(shù)據(jù)的世界里,時(shí)間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準(zhǔn) ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應(yīng)用與實(shí)戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認(rèn)證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗(yàn):數(shù)據(jù)趨勢(shì)與突變分析的有力工具? ? ? 在數(shù)據(jù)分析的廣袤領(lǐng)域中,準(zhǔn)確捕捉數(shù)據(jù)的趨勢(shì)變化以及識(shí)別 ...
2025-07-08備戰(zhàn) CDA 數(shù)據(jù)分析師考試:需要多久?如何規(guī)劃? CDA(Certified Data Analyst)數(shù)據(jù)分析師認(rèn)證作為國(guó)內(nèi)權(quán)威的數(shù)據(jù)分析能力認(rèn)證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應(yīng)對(duì)策略? 長(zhǎng)短期記憶網(wǎng)絡(luò)(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)的一種變體,憑借獨(dú)特的門控機(jī)制,在 ...
2025-07-07統(tǒng)計(jì)學(xué)方法在市場(chǎng)調(diào)研數(shù)據(jù)中的深度應(yīng)用? 市場(chǎng)調(diào)研是企業(yè)洞察市場(chǎng)動(dòng)態(tài)、了解消費(fèi)者需求的重要途徑,而統(tǒng)計(jì)學(xué)方法則是市場(chǎng)調(diào)研數(shù) ...
2025-07-07CDA數(shù)據(jù)分析師證書考試全攻略? 在數(shù)字化浪潮席卷全球的當(dāng)下,數(shù)據(jù)已成為企業(yè)決策、行業(yè)發(fā)展的核心驅(qū)動(dòng)力,數(shù)據(jù)分析師也因此成為 ...
2025-07-07剖析 CDA 數(shù)據(jù)分析師考試題型:解鎖高效備考與答題策略? CDA(Certified Data Analyst)數(shù)據(jù)分析師考試作為衡量數(shù)據(jù)專業(yè)能力的 ...
2025-07-04SQL Server 字符串截取轉(zhuǎn)日期:解鎖數(shù)據(jù)處理的關(guān)鍵技能? 在數(shù)據(jù)處理與分析工作中,數(shù)據(jù)格式的規(guī)范性是保證后續(xù)分析準(zhǔn)確性的基礎(chǔ) ...
2025-07-04CDA 數(shù)據(jù)分析師視角:從數(shù)據(jù)迷霧中探尋商業(yè)真相? 在數(shù)字化浪潮席卷全球的今天,數(shù)據(jù)已成為企業(yè)決策的核心驅(qū)動(dòng)力,CDA(Certifie ...
2025-07-04CDA 數(shù)據(jù)分析師:開啟數(shù)據(jù)職業(yè)發(fā)展新征程? ? 在數(shù)據(jù)成為核心生產(chǎn)要素的今天,數(shù)據(jù)分析師的職業(yè)價(jià)值愈發(fā)凸顯。CDA(Certified D ...
2025-07-03