<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          沒有什么內(nèi)存問題,是一行 Python 代碼解決不了的

          共 4448字,需瀏覽 9分鐘

           ·

          2020-10-04 15:43

          內(nèi)存不足是項(xiàng)目開發(fā)過程中經(jīng)常碰到的問題,我和我的團(tuán)隊(duì)在之前的一個(gè)項(xiàng)目中也遇到了這個(gè)問題,我們的項(xiàng)目需要存儲和處理一個(gè)相當(dāng)大的動態(tài)列表,測試人員經(jīng)常向我抱怨內(nèi)存不足。但是最終,我們通過添加一行簡單的代碼解決了這個(gè)問題。


          結(jié)果如圖所示:


          我將在下面解釋它的工作原理。


          舉一個(gè)簡單的“l(fā)earning”示例 - 創(chuàng)建一個(gè)DataItem類,在其中定義一些個(gè)人信息屬性,例如姓名,年齡和地址。

          class?DataItem(object):
          ???def?__init__(self,?name,?age,?address):
          ???????self.name?=?name
          ???????self.age?=?age
          ???????self.address?=?address


          小測試——這樣一個(gè)對象會占用多少內(nèi)存?


          首先讓我們嘗試下面這種測試方案:

          d1?=?DataItem("Alex",?42,?"-")
          print?("sys.getsizeof(d1):",?sys.getsizeof(d1))


          答案是56字節(jié)??雌饋肀容^小,結(jié)果令人滿意。


          但是,讓我們檢查另一個(gè)數(shù)據(jù)多一些的對象:

          d2?=?DataItem("Boris",?24,?"In?the?middle?of?nowhere")
          print?("sys.getsizeof(d2):",?sys.getsizeof(d2))


          答案仍然是56。這讓我們明白這個(gè)結(jié)果并不完全正確。


          我們的直覺是對的,這個(gè)問題不是那么簡單。Python是一種非常靈活的語言,具有動態(tài)類型,它在工作時(shí)存儲了許多額外的數(shù)據(jù)。這些額外的數(shù)據(jù)本身就占了很多內(nèi)存。


          例如,sys.getsizeof(“ ”)返回33,沒錯,每個(gè)空行就多達(dá)33字節(jié)!并且sys.getsizeof(1)將為此數(shù)字返回24-24個(gè)字節(jié)(我建議C程序員們現(xiàn)在點(diǎn)擊結(jié)束閱讀,以免對Python的美麗失去信心)。


          對于更復(fù)雜的元素,例如字典,sys.getsizeof(dict())返回272個(gè)字節(jié),這還只是一個(gè)空字典。舉例到此為止,但事實(shí)已經(jīng)很清楚了,何況RAM的制造商也需要出售他們的芯片。


          現(xiàn)在,讓我們回到回到我們的DataItem類和“小測試”問題。


          這個(gè)類到底占多少內(nèi)存?


          首先,我們將以較低級別輸出該類的全部內(nèi)容:

          def?dump(obj):
          ????for?attr?in?dir(obj):
          ????????print("??obj.%s?=?%r"?%?(attr,?getattr(obj,?attr)))


          這個(gè)函數(shù)將顯示隱藏在“隱身衣”下的內(nèi)容,以便所有Python函數(shù)(類型,繼承和其他包)都可以運(yùn)行。


          結(jié)果令人印象深刻:


          它總共占用多少內(nèi)存呢?


          在GitHub上,有一個(gè)函數(shù)可以計(jì)算實(shí)際大小,通過遞歸調(diào)用所有對象的getsizeof實(shí)現(xiàn)。

          def?get_size(obj,?seen=None):
          ???#?From?https://goshippo.com/blog/measure-real-size-any-python-object/
          ???#?Recursively?finds?size?of?objects
          ???size?=?sys.getsizeof(obj)
          ???if?seen?is?None:
          ???????seen?=?set()
          ???obj_id?=?id(obj)
          ???if?obj_id?in?seen:
          ???????return?0

          #?Important?mark?as?seen?*before*?entering?recursion?to?gracefully?handle
          ???#?self-referential?objects
          ???seen.add(obj_id)
          ???if?isinstance(obj,?dict):
          ?????size?+=?sum([get_size(v,?seen)?for?v?in?obj.values()])
          ?????size?+=?sum([get_size(k,?seen)?for?k?in?obj.keys()])
          ???elif?hasattr(obj,?'__dict__'):
          ?????size?+=?get_size(obj.__dict__,?seen)
          ???elif?hasattr(obj,?'__iter__')?and?not?isinstance(obj,?(str,?bytes,?bytearray)):
          ?????size?+=?sum([get_size(i,?seen)?for?i?in?obj])
          ???return?size


          #讓我們試一下:

          d1?=?DataItem("Alex",?42,?"-")
          print?("get_size(d1):",?get_size(d1))

          d2?=?DataItem("Boris",?24,?"In?the?middle?of?nowhere")
          print?("get_size(d2):",?get_size(d2))


          我們分別得到460和484字節(jié),這似乎更接近事實(shí)。


          使用這個(gè)函數(shù),我們可以進(jìn)行一系列實(shí)驗(yàn)。例如,我想知道如果DataItem放在列表中,數(shù)據(jù)將占用多少空間。


          get_size([d1])函數(shù)返回532個(gè)字節(jié),顯然,這些是“原本的”460+一些額外開銷。但是get_size([d1,d2])返回863個(gè)字節(jié)—小于460+484。get_size([d1,d2,d1])的結(jié)果更加有趣,它產(chǎn)生了871個(gè)字節(jié),只是稍微多了一點(diǎn),這說明Python很聰明,不會再為同一個(gè)對象分配內(nèi)存。


          現(xiàn)在我們來看問題的第二部分。


          是否有可能減少內(nèi)存消耗?


          答案是肯定的。Python是一個(gè)解釋器,我們可以隨時(shí)擴(kuò)展我們的類,例如,添加一個(gè)新字段:

          d1?=?DataItem("Alex",?42,?"-")
          print?("get_size(d1):",?get_size(d1))

          d1.weight?=?66
          print?("get_size(d1):",?get_size(d1))


          這是一個(gè)很棒的特點(diǎn),但是如果我們不需要這個(gè)功能,我們可以強(qiáng)制解釋器使用__slots__指令來指定類屬性列表:

          class?DataItem(object):
          ???__slots__?=?['name',?'age',?'address']
          ???def?__init__(self,?name,?age,?address):
          ???????self.name?=?name
          ???????self.age?=?age
          ???????self.address?=?address


          更多信息可以參考文檔中的“__dict__和__weakref__的部分。使用__dict__所節(jié)省的空間可能會很大”。


          我們嘗試后發(fā)現(xiàn):get_size(d1)返回的是64字節(jié),對比460直接,減少約7倍。作為獎勵,對象的創(chuàng)建速度提高了約20%(請參閱文章的第一個(gè)屏幕截圖)。


          真正使用如此大的內(nèi)存增益不會導(dǎo)致其他開銷成本。只需添加元素即可創(chuàng)建100,000個(gè)數(shù)組,并查看內(nèi)存消耗:

          data?=?[]
          for?p?in?range(100000):
          ???data.append(DataItem("Alex",?42,?"middle?of?nowhere"))

          snapshot?=?tracemalloc.take_snapshot()
          top_stats?=?snapshot.statistics('lineno')
          total?=?sum(stat.size?for?stat?in?top_stats)
          print("Total?allocated?size:?%.1f?MB"?%?(total?/?(1024*1024)))


          在沒有__slots__的情況結(jié)果為16.8MB,而使用__slots__時(shí)為6.9MB。當(dāng)然不是7倍,但考慮到代碼變化很小,它的表現(xiàn)依然出色。


          現(xiàn)在討論一下這種方式的缺點(diǎn)。激活__slots__會禁止創(chuàng)建其他所有元素,包括__dict__,這意味著,例如,下面這種將結(jié)構(gòu)轉(zhuǎn)換為json的代碼將不起作用:

          def?toJSON(self):
          ????return?json.dumps(self.__dict__)


          但這也很容易搞定,可以通過編程方式生成你的dict,遍歷循環(huán)中的所有元素:

          def?toJSON(self):
          ???????data?=?dict()
          ???????for?var?in?self.__slots__:
          ???????????data[var]?=?getattr(self,?var)
          ???????return?json.dumps(data)


          向類中動態(tài)添加新變量也是不可能的,但在我們的項(xiàng)目里,這不是必需的。


          下面是最后一個(gè)小測試。來看看整個(gè)程序需要多少內(nèi)存。在程序末尾添加一個(gè)無限循環(huán),使其持續(xù)運(yùn)行,并查看Windows任務(wù)管理器中的內(nèi)存消耗。


          沒有__slots__時(shí)



          69Mb變成27Mb......好吧,畢竟我們節(jié)省了內(nèi)存。對于只添加一行代碼的結(jié)果來說已經(jīng)很好了。


          注意:tracemalloc調(diào)試庫使用了大量額外的內(nèi)存。顯然,它為每個(gè)創(chuàng)建的對象添加了額外的元素。如果你將其關(guān)閉,總內(nèi)存消耗將會少得多,截圖顯示了2個(gè)選項(xiàng):



          如何節(jié)省更多的內(nèi)存?


          可以使用numpy庫,它允許你以C風(fēng)格創(chuàng)建結(jié)構(gòu),但在這個(gè)的項(xiàng)目中,它需要更深入地改進(jìn)代碼,所以對我來說第一種方法就足夠了。


          奇怪的是,__slots__的使用從未在Habré上詳細(xì)分析過,我希望這篇文章能夠填補(bǔ)這一空白。


          結(jié)論


          這篇文章看起來似乎是反Python的廣告,但它根本不是。Python是非常可靠的(為了“刪除”Python中的程序,你必須非常努力),這是一種易于閱讀和方便編寫的語言。在許多情況下,這些優(yōu)點(diǎn)遠(yuǎn)勝過缺點(diǎn),但如果你需要性能和效率的最大化,你可以使用numpy庫像C++一樣編寫代碼,它可以非??焖儆行У靥幚頂?shù)據(jù)。


          最后,祝你編程愉快!

          相關(guān)報(bào)道:?

          https://medium.com/@alexmaisiura/python-how-to-reduce-memory-consumption-by-half-by-adding-just-one-line-of-code-56be6443d524

          源:大數(shù)據(jù)文摘

          編譯:Javen、胡笳、云舟

          版權(quán)申明:內(nèi)容來源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無法確認(rèn),我們都會標(biāo)明作者及出處,如有侵權(quán)煩請告知,我們會立即刪除并表示歉意。謝謝!





          感謝閱讀



          瀏覽 69
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线黄片网站 | 大香蕉第一页 | 无码在线观看第一页 | 学生妹一级 片内射视频 | aaa三级黄片 |