日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

如何降低 Python 的內(nèi)存消耗量?

系統(tǒng) 2095 0
原文鏈接: https://data.newrank.cn/m/s.html?s=PigpOzA/LTE%3D

640?wx_fmt=gif

在程序執(zhí)行期間,如果內(nèi)存中存在大量處于活動(dòng)狀態(tài)的對(duì)象,就有可能出現(xiàn)內(nèi)存問題,尤其是在可用內(nèi)存總量有限的情況下。在本文中,我們將討論通過縮小對(duì)象大幅減少Python所需內(nèi)存量的方法。

如何降低 Python 的內(nèi)存消耗量?_第1張圖片

作者 |? intellimath

譯者 |?彎月,責(zé)編 | 郭芮

出品 | CSDN(ID:CSDNnews)

以下為譯文:

為了簡(jiǎn)便起見,我們以一個(gè)表示點(diǎn)的Python結(jié)構(gòu)為例,它包括x、y、z坐標(biāo)值,坐標(biāo)值可以通過名稱訪問。

?

640?wx_fmt=png

Dict

?

在小型程序中,特別是在腳本中,使用Python自帶的dict來表示結(jié)構(gòu)信息非常簡(jiǎn)單方便:

            
              
                >>
              
              >?ob?=?{
              
                'x'
              
              
                :1
              
              ,?
              
                'y'
              
              
                :2
              
              ,?
              
                'z'
              
              
                :3
              
              }

              
                >>
              
              >?x?=?ob[
              
                'x'
              
              ]

              
                >>
              
              >?ob[
              
                'y'
              
              ]?=?y
            
          

?

由于在Python 3.6中dict的實(shí)現(xiàn)采用了一組有序鍵,因此其結(jié)構(gòu)更為緊湊,更深得人心。但是,讓我們看看dict在內(nèi)容中占用的空間大?。?

            
              
                >>
              
              >?print(sys.getsizeof(ob))

              
                240
              
            
          

?

如上所示,dict占用了大量?jī)?nèi)存,尤其是如果突然虛需要?jiǎng)?chuàng)建大量實(shí)例時(shí):

實(shí)例數(shù)

對(duì)象大小

1 000 000

240 Mb

10 000 000

2.40 Gb

100 000 000

24 Gb

?

640?wx_fmt=png

類實(shí)例

?

有些人希望將所有東西都封裝到類中,他們更喜歡將結(jié)構(gòu)定義為可以通過屬性名訪問的類:

            
              
                
                  class
                
                ?
                
                  Point
                
                :
              
              
????
              
                #
              
              
????
              
                def?
                
                  __init__
                
                
                  (
                  
                    self
                  
                  ,?x,?y,?z)
                
              
              :
????????
              
                self
              
              .x?=?x
????????
              
                self
              
              .y?=?y
????????
              
                self
              
              .z?=?z


              
                >>
              
              >?ob?=?Point(
              
                1
              
              ,
              
                2
              
              ,
              
                3
              
              )

              
                >>
              
              >?x?=?ob.x

              
                >>
              
              >?ob.y?=?y
            
          

類實(shí)例的結(jié)構(gòu)很有趣:

字段

大?。ū忍兀?

PyGC_Head

24

PyObject_HEAD

16

__weakref__

8

__dict__

8

合計(jì):

56

在上表中,__weakref__是該列表的引用,稱之為到該對(duì)象的弱引用(weak reference);字段__dict__是該類的實(shí)例字典的引用,其中包含實(shí)例屬性的值(注意在64-bit引用平臺(tái)中占用8字節(jié))。從Python3.3開始,所有類實(shí)例的字典的鍵都存儲(chǔ)在共享空間中。這樣就減少了內(nèi)存中實(shí)例的大小:

            
              >>>?
              
                print
              
              (
              
                sys
              
              
                .getsizeof
              
              (
              
                ob
              
              ),?
              
                sys
              
              
                .getsizeof
              
              (
              
                ob
              
              
                .__dict__
              
              ))?
56?112
            
          

因此,大量類實(shí)例在內(nèi)存中占用的空間少于常規(guī)字典(dict):

實(shí)例數(shù)

大小

1 000 000

168 Mb

10 000 000

1.68 Gb

100 000 000

16.8 Gb

不難看出,由于實(shí)例的字典很大,所以實(shí)例依然占用了大量?jī)?nèi)存。

?

640?wx_fmt=png

帶有__slots__的類實(shí)例

?

為了大幅降低內(nèi)存中類實(shí)例的大小,我們可以考慮干掉__dict__和__weakref__。為此,我們可以借助 __slots__:

            
              
                
                  class
                
                ?
                
                  Point
                
                :
              
              
????__slots_
              
                _
              
              ?=?
              
                'x'
              
              ,?
              
                'y'
              
              ,?
              
                'z'
              
              

????
              
                def?
                
                  __init__
                
                
                  (
                  
                    self
                  
                  ,?x,?y,?z)
                
              
              :
????????
              
                self
              
              .x?=?x
????????
              
                self
              
              .y?=?y
????????
              
                self
              
              .z?=?z


              
                >>
              
              >?ob?=?Point(
              
                1
              
              ,
              
                2
              
              ,
              
                3
              
              )

              
                >>
              
              >?print(sys.getsizeof(ob))

              
                64
              
            
          

?

如此一來,內(nèi)存中的對(duì)象就明顯變小了:

字段

大?。ū忍兀?

PyGC_Head

24

PyObject_HEAD

16

x

8

y

8

z

8

總計(jì):

64

在類的定義中使用了__slots__以后,大量實(shí)例占據(jù)的內(nèi)存就明顯減少了:

實(shí)例數(shù)

大小

1 000 000

64 Mb

10 000 000

640 Mb

100 000 000

6.4 Gb

目前,這是降低類實(shí)例占用內(nèi)存的主要方式。

這種方式減少內(nèi)存的原理為:在內(nèi)存中,對(duì)象的標(biāo)題后面存儲(chǔ)的是對(duì)象的引用(即屬性值),訪問這些屬性值可以使用類字典中的特殊描述符:

            
              >>>?pprint(Point.__dict__)
mappingproxy(
??????????????....................................
??????????????
              
                'x'
              
              :?
              
                'x'?
                
                  of
                
                ?
                
                  'Point'
                
                ?objects>,
??????????????
                
                  'y'
                
                :?
                
                  'y'?
                  
                    of
                  
                  ?
                  
                    'Point'
                  
                  ?objects>,
??????????????
                  
                    'z'
                  
                  :?
                  
                    'z'?
                    
                      of
                    
                    ?
                    
                      'Point'
                    
                    ?objects>})
                  
                
              
            
          

?

為了自動(dòng)化使用__slots__創(chuàng)建類的過程,你可以使用庫namedlist(https://pypi.org/project/namedlist)。namedlist.namedlist函數(shù)可以創(chuàng)建帶有__slots__的類:

            
              
                >>
              
              >?Point?=?namedlist(
              
                'Point'
              
              ,?(
              
                'x'
              
              ,?
              
                'y'
              
              ,?
              
                'z'
              
              ))
            
          

?

還有一個(gè)包attrs(https://pypi.org/project/attrs),無論使用或不使用__slots__都可以利用這個(gè)包自動(dòng)創(chuàng)建類。

?

640?wx_fmt=png

元組

?

Python還有一個(gè)自帶的元組(tuple)類型,代表不可修改的數(shù)據(jù)結(jié)構(gòu)。元組是固定的結(jié)構(gòu)或記錄,但它不包含字段名稱。你可以利用字段索引訪問元組的字段。在創(chuàng)建元組實(shí)例時(shí),元組的字段會(huì)一次性關(guān)聯(lián)到值對(duì)象:

            
              
                >>
              
              >?ob?=?(
              
                1
              
              ,
              
                2
              
              ,
              
                3
              
              )

              
                >>
              
              >?x?=?ob[
              
                0
              
              ]

              
                >>
              
              >?ob[
              
                1
              
              ]?=?y?
              
                #?ERROR
              
            
          

?

元組實(shí)例非常緊湊:

            
              
                >>
              
              >?print(sys.getsizeof(ob))

              
                72
              
            
          

?

由于內(nèi)存中的元組還包含字段數(shù),因此需要占據(jù)內(nèi)存的8個(gè)字節(jié),多于帶有__slots__的類:

字段

大小(字節(jié))

PyGC_Head

24

PyObject_HEAD

16

ob_size

8

[0]

8

[1]

8

[2]

8

總計(jì):

72

?

640?wx_fmt=png

命名元組

?

由于元組的使用非常廣泛,所以終有一天你需要通過名稱訪問元組。為了滿足這種需求,你可以使用模塊collections.namedtuple。

namedtuple函數(shù)可以自動(dòng)生成這種類:

            
              
                >>
              
              >?Point?=?namedtuple(
              
                'Point'
              
              ,?(
              
                'x'
              
              ,?
              
                'y'
              
              ,?
              
                'z'
              
              ))
            
          

如上代碼創(chuàng)建了元組的子類,其中還定義了通過名稱訪問字段的描述符。對(duì)于上述示例,訪問方式如下:

            
              ?
              
                
                  class
                
                ?
                
                  Point
                
                (
                
                  tuple
                
                ):
              
              
?????
              
                #
              
              
?????@property
?????
              
                def?
                
                  _get_x
                
                
                  (
                  
                    self
                  
                  )
                
              
              :
?????????
              
                return
              
              ?
              
                self
              
              [
              
                0
              
              ]
?????@property
?????
              
                def?
                
                  _get_y
                
                
                  (
                  
                    self
                  
                  )
                
              
              :
?????????
              
                return
              
              ?
              
                self
              
              [
              
                1
              
              ]
?????@property
?????
              
                def?
                
                  _get_z
                
                
                  (
                  
                    self
                  
                  )
                
              
              :
?????????
              
                return
              
              ?
              
                self
              
              [
              
                2
              
              ]
?????
              
                #
              
              
?????
              
                def?
                
                  __new__
                
                
                  (cls,?x,?y,?z)
                
              
              :
?????????
              
                return
              
              ?tuple.__new_
              
                _
              
              (cls,?(x,?y,?z))

            
          

這種類所有的實(shí)例所占用的內(nèi)存與元組完全相同。但大量的實(shí)例占用的內(nèi)存也會(huì)稍稍多一些:

實(shí)例數(shù)

大小

1 000 000

72 Mb

10 000 000

720 Mb

100 000 000

7.2 Gb

?

640?wx_fmt=png

記錄類:不帶循環(huán)GC的可變更命名元組

?

由于元組及其相應(yīng)的命名元組類能夠生成不可修改的對(duì)象,因此類似于ob.x的對(duì)象值不能再被賦予其他值,所以有時(shí)還需要可修改的命名元組。由于Python沒有相當(dāng)于元組且支持賦值的內(nèi)置類型,因此人們想了許多辦法。在這里我們討論一下記錄類(recordclass,https://pypi.org/project/recordclass),它在StackoverFlow上廣受好評(píng)(https://stackoverflow.com/questions/29290359/existence-of-mutable-named-tuple-in)。

此外,它還可以將對(duì)象占用的內(nèi)存量減少到與元組對(duì)象差不多的水平。

recordclass包引入了類型recordclass.mutabletuple,它幾乎等價(jià)于元組,但它支持賦值。它會(huì)創(chuàng)建幾乎與namedtuple完全一致的子類,但支持給屬性賦新值(而不需要?jiǎng)?chuàng)建新的實(shí)例)。recordclass函數(shù)與namedtuple函數(shù)類似,可以自動(dòng)創(chuàng)建這些類:

            
              ?
              
                >>>?
              
              Point?=?recordclass(
              
                'Point'
              
              ,?(
              
                'x'
              
              ,?
              
                'y'
              
              ,?
              
                'z'
              
              ))
?
              
                >>>?
              
              ob?=?Point(
              
                1
              
              ,?
              
                2
              
              ,?
              
                3
              
              )
            
          

?

類實(shí)例的結(jié)構(gòu)也類似于tuple,但沒有PyGC_Head:

字段

大小(字節(jié))

PyObject_HEAD

16

ob_size

8

x

8

y

8

z

8

總計(jì):

48

?

在默認(rèn)情況下,recordclass函數(shù)會(huì)創(chuàng)建一個(gè)類,該類不參與垃圾回收機(jī)制。一般來說,namedtuple和recordclass都可以生成表示記錄或簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)(即非遞歸結(jié)構(gòu))的類。在Python中正確使用這二者不會(huì)造成循環(huán)引用。因此,recordclass生成的類實(shí)例默認(rèn)情況下不包含PyGC_Head片段(這個(gè)片段是支持循環(huán)垃圾回收機(jī)制的必需字段,或者更準(zhǔn)確地說,在創(chuàng)建類的PyTypeObject結(jié)構(gòu)中,flags字段默認(rèn)情況下不會(huì)設(shè)置Py_TPFLAGS_HAVE_GC標(biāo)志)。

大量實(shí)例占用的內(nèi)存量要小于帶有__slots__的類實(shí)例:

實(shí)例數(shù)

大小

1 000 000

48 Mb

10 000 000

480 Mb

100 000 000

4.8 Gb

?

640?wx_fmt=png

dataobject

?

recordclass庫提出的另一個(gè)解決方案的基本想法為:內(nèi)存結(jié)構(gòu)采用與帶__slots__的類實(shí)例同樣的結(jié)構(gòu),但不參與循環(huán)垃圾回收機(jī)制。這種類可以通過recordclass.make_dataclass函數(shù)生成:

            
              
                >>
              
              >?Point?=?make_dataclass(
              
                'Point'
              
              ,?(
              
                'x'
              
              ,?
              
                'y'
              
              ,?
              
                'z'
              
              ))
            
          

這種方式創(chuàng)建的類默認(rèn)會(huì)生成可修改的實(shí)例。

另一種方法是從recordclass.dataobject繼承:

            
              
                class
              
              ?Point(dataobject):
????x:
              
                int
              
              
????y:
              
                int
              
              
????z:
              
                int
              
            
          

?

這種方法創(chuàng)建的類實(shí)例不會(huì)參與循環(huán)垃圾回收機(jī)制。內(nèi)存中實(shí)例的結(jié)構(gòu)與帶有__slots__的類相同,但沒有PyGC_Head:

字段

大?。ㄗ止?jié))

PyObject_HEAD

16

ob_size

8

x

8

y

8

z

8

總計(jì):

48

?

            
              
                >>
              
              >?ob?=?Point(
              
                1
              
              ,
              
                2
              
              ,
              
                3
              
              )

              
                >>
              
              >?print(sys.getsizeof(ob))

              
                40
              
            
          

?

如果想訪問字段,則需要使用特殊的描述符來表示從對(duì)象開頭算起的偏移量,其位置位于類字典內(nèi):

            
              mappingproxy({
              
                '__new__'
              
              :?
              
                0x7f203c4e6be0>,
??????????????.......................................
??????????????
                
                  'x'
                
                :?
                
                  0x7f203c55c690>,
??????????????
                  
                    'y'
                  
                  :?
                  
                    0x7f203c55c670>,
??????????????
                    
                      'z'
                    
                    :?
                    
                      0x7f203c55c410>})
                    
                  
                
              
            
          

大量實(shí)例占用的內(nèi)存量在CPython實(shí)現(xiàn)中是最小的:

實(shí)例數(shù)

大小

1 000 000

40 Mb

10 000 000

400 Mb

100 000 000

4.0 Gb

?

640?wx_fmt=png

Cython

?

還有一個(gè)基于Cython(https://cython.org/)的方案。該方案的優(yōu)點(diǎn)是字段可以使用C語言的原子類型。訪問字段的描述符可以通過純Python創(chuàng)建。例如:

            
              cdef?
              
                
                  class
                
                ?
                
                  Python
                
                :
              
              
????cdef?public?int?x,?y,?z

?
              
                def?
                
                  __init__
                
                
                  (
                  
                    self
                  
                  ,?x,?y,?z)
                
              
              :
??????
              
                self
              
              .x?=?x
??????
              
                self
              
              .y?=?y
??????
              
                self
              
              .z?=?z
            
          

?

本例中實(shí)例占用的內(nèi)存更小:

            
              
                >>
              
              >?ob?=?Point(
              
                1
              
              ,
              
                2
              
              ,
              
                3
              
              )

              
                >>
              
              >?print(sys.getsizeof(ob))

              
                32
              
            
          

內(nèi)存結(jié)構(gòu)如下:

字段

大?。ㄗ止?jié))

PyObject_HEAD

16

x

4

y

4

z

4

nycto

4

總計(jì):

32

大量副本所占用的內(nèi)存量也很?。?

實(shí)例數(shù)

大小

1 000 000

32 Mb

10 000 000

320 Mb

100 000 000

3.2 Gb

但是,需要記住在從Python代碼訪問時(shí),每次訪問都會(huì)引發(fā)int類型和Python對(duì)象之間的轉(zhuǎn)換。

?

640?wx_fmt=png

Numpy

?

使用擁有大量數(shù)據(jù)的多維數(shù)組或記錄數(shù)組會(huì)占用大量?jī)?nèi)存。但是,為了有效地利用純Python處理數(shù)據(jù),你應(yīng)該使用Numpy包提供的函數(shù)。

            
              >>>?Point?=?numpy.dtype((
              
                'x'
              
              ,?numpy.
              
                int32
              
              ),?(
              
                'y'
              
              ,?numpy.
              
                int32
              
              ),?(
              
                'z'
              
              ,?numpy.
              
                int32
              
              )])

            
          

一個(gè)擁有N個(gè)元素、初始化成零的數(shù)組可以通過下面的函數(shù)創(chuàng)建:

            
              ?
              
                >>>?
              
              points?=?numpy.zeros(N,?dtype=Point)
            
          

內(nèi)存占用是最小的:

實(shí)例數(shù)

大小

1 000 000

12 Mb

10 000 000

120 Mb

100 000 000

1.2 Gb

一般情況下,訪問數(shù)組元素和行會(huì)引發(fā)Python對(duì)象與C語言int值之間的轉(zhuǎn)換。如果從生成的數(shù)組中獲取一行結(jié)果,其中包含一個(gè)元素,其內(nèi)存就沒那么緊湊了:

            
              ??>>>?
              
                sys
              
              
                .getsizeof
              
              (
              
                points
              
              
                [0]
              
              )
??68
            
          

因此,如上所述,在Pytho代碼中需要使用numpy包提供的函數(shù)來處理數(shù)組。

?

640?wx_fmt=png

總結(jié)

?

在本文中,我們通過一個(gè)簡(jiǎn)單明了的例子,求證了Python語言(CPython)社區(qū)的開發(fā)人員和用戶可以真正減少對(duì)象占用的內(nèi)存量。

原文:https://habr.com/en/post/458518/

本文為 CSDN 翻譯,轉(zhuǎn)載請(qǐng)注明來源出處。

學(xué)Python想要達(dá)到大牛高度,你得這么學(xué)!

https://edu.csdn.net/topic/python115?utm_source=csdn_bw

【END】

如何降低 Python 的內(nèi)存消耗量?_第2張圖片

?熱 文 ?推 薦?

? “5 年內(nèi),PC 或?qū)⒅饾u消失!”| 人物志

? 直接拿來用!GitHub 標(biāo)星 5000+,學(xué)生黨學(xué)編程有這份資料就夠了

? 華人學(xué)者解開計(jì)算機(jī)領(lǐng)域 30 年難題:布爾函數(shù)敏感度猜想

?真實(shí)揭秘 90 后程序員奔三準(zhǔn)備:有人學(xué)金融投資,有人想當(dāng)全棧工程師!

?天網(wǎng)恢恢!又一名暗網(wǎng)比特幣洗錢者被抓了

?乘勢(shì)而起,走進(jìn)2019年風(fēng)口“邊緣計(jì)算”

?Python之父新發(fā)文,將替換現(xiàn)有解析器

?超全!深度學(xué)習(xí)在計(jì)算機(jī)視覺領(lǐng)域的應(yīng)用一覽

?中國(guó)第一程序員,微軟得不到他就要?dú)Я怂?

如何降低 Python 的內(nèi)存消耗量?_第3張圖片 點(diǎn)擊閱讀原文,輸入關(guān)鍵詞,即可搜索您想要的 CSDN 文章。

640?wx_fmt=png 你點(diǎn)的每個(gè)“在看”,我都認(rèn)真當(dāng)成了喜歡


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦?。?!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 新干县| 河北区| 安福县| 鹿泉市| 广宁县| 贞丰县| 保德县| 明光市| 东阳市| 搜索| 酉阳| 神农架林区| 古蔺县| 康平县| 锦屏县| 涿鹿县| 东至县| 九江市| 静海县| 平顺县| 平阳县| 临西县| 洪洞县| 怀来县| 东城区| 陇西县| 拜城县| 桦甸市| 平阴县| 鹿邑县| 万源市| 博罗县| 长泰县| 木里| 社旗县| 饶河县| 西乌珠穆沁旗| 武平县| 板桥市| 米易县| 泰来县|