99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線電話:13121318867

登錄
首頁(yè)精彩閱讀python在計(jì)算內(nèi)存時(shí)應(yīng)該注意的問題?
python在計(jì)算內(nèi)存時(shí)應(yīng)該注意的問題?
2020-10-16
收藏

作者:豌豆花下貓

來源:Python貓

我之前的一篇文章,帶大家揭曉了python 在給內(nèi)置對(duì)象分配內(nèi)存時(shí)的 5 個(gè)奇怪而有趣的小秘密。文中使用了sys.getsizeof()來計(jì)算內(nèi)存,但是用這個(gè)方法計(jì)算時(shí),可能會(huì)出現(xiàn)意料不到的問題。

文檔中關(guān)于這個(gè)方法的介紹有兩層意思:

  • 該方法用于獲取一個(gè)對(duì)象的字節(jié)大?。╞ytes)
  • 它只計(jì)算直接占用的內(nèi)存,而不計(jì)算對(duì)象內(nèi)所引用對(duì)象的內(nèi)存

也就是說,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ā)散地思考如下的問題:

  • “淺計(jì)算”方法的底層實(shí)現(xiàn)是怎樣的?
  • 為什么 getsizeof() 會(huì)采用“淺計(jì)算”的方法?

關(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ū)別:

  • 字節(jié)增大:int 類型在 C 語(yǔ)言中只占到 4 個(gè)字節(jié),但是在 Python 中,int 其實(shí)是被封裝成了一個(gè)對(duì)象,所以在計(jì)算其大小時(shí),會(huì)包含對(duì)象結(jié)構(gòu)體的大小。在 32 位解釋器中,getsizeof(1) 的結(jié)果是 14 個(gè)字節(jié),比數(shù)字本身的 4 字節(jié)增大了。
  • 字節(jié)減少:對(duì)于相對(duì)復(fù)雜的對(duì)象,例如列表和字典,這套計(jì)算機(jī)制由于沒有累加內(nèi)部元素的占用量,就會(huì)出現(xiàn)比真實(shí)占用內(nèi)存小的結(jié)果。

由此,我有一個(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)該注意的問題有:

  • 是否存在“深計(jì)算”的方法/實(shí)現(xiàn)方案?
  • 實(shí)現(xiàn)“深計(jì)算”時(shí)應(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è)班”了解課程詳情;

想了解更多優(yōu)質(zhì)課程,請(qǐng)點(diǎn)擊>>>

數(shù)據(jù)分析咨詢請(qǐng)掃描二維碼

若不方便掃碼,搜微信號(hào):CDAshujufenxi

數(shù)據(jù)分析師資訊
更多

OK
客服在線
立即咨詢
客服在線
立即咨詢
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機(jī)數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調(diào)用 initGeetest 進(jìn)行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調(diào),回調(diào)的第一個(gè)參數(shù)驗(yàn)證碼對(duì)象,之后可以使用它調(diào)用相應(yīng)的接口 initGeetest({ // 以下 4 個(gè)配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶后臺(tái)檢測(cè)極驗(yàn)服務(wù)器是否宕機(jī) new_captcha: data.new_captcha, // 用于宕機(jī)時(shí)表示是新驗(yàn)證碼的宕機(jī) product: "float", // 產(chǎn)品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說明請(qǐng)參見:http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計(jì)時(shí)完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請(qǐng)輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請(qǐng)輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }