中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Python 內(nèi)存優(yōu)化

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線(xiàn)!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

實(shí)際項(xiàng)目中,pythoner更加關(guān)注的是Python的性能問(wèn)題,之前也寫(xiě)過(guò)一篇文章《 Python性能優(yōu)化 》介紹Python性能優(yōu)化的一些方法。而本文,關(guān)注的是Python的內(nèi)存優(yōu)化,一般說(shuō)來(lái),如果不發(fā)生內(nèi)存泄露,運(yùn)行在服務(wù)端的Python代碼不用太關(guān)心內(nèi)存,但是如果運(yùn)行在客戶(hù)端(比如移動(dòng)平臺(tái)上),那還是有優(yōu)化的必要。具體而言,本文主要針對(duì)的Cpython,而且不涉及C擴(kuò)展。

我們知道,Python使用引用技術(shù)和垃圾回收來(lái)管理內(nèi)存,底層也有各種類(lèi)型的內(nèi)存池,那我們?cè)趺吹弥欢未a使用的內(nèi)存情況呢?工欲善其事必先利其器,直接看windows下的任務(wù)管理器或者linux下的top肯定是不準(zhǔn)的。

Pytracemalloc

對(duì)于基本類(lèi)型,可以通過(guò)sys.getsizeof()來(lái)查看對(duì)象占用的內(nèi)存大小。以下是在64位Linux下的一些結(jié)果:

>>> import sys
 
>>> sys.getsizeof(1)
24
>>> sys.getsizeof([])
72
>>> sys.getsizeof(())
56
>>> sys.getsizeof({})
280
>>> sys.getsizeof(True)
24

可以看到,即使是一個(gè)int類(lèi)型(1)也需要占用24個(gè)字節(jié),遠(yuǎn)遠(yuǎn)高于C語(yǔ)言中int的范圍。因?yàn)镻ython中一切都是對(duì)象,int也不例外(事實(shí)上是PyIntObject),除了真正存儲(chǔ)的數(shù)值,還需要保存引用計(jì)數(shù)信息、類(lèi)型信息,更具體的可以參見(jiàn)《Python源碼剖析》。

而對(duì)于更復(fù)雜的組合類(lèi)型,復(fù)雜的代碼,使用getsizeof來(lái)查看就不準(zhǔn)確了,因?yàn)樵赑ython中變量?jī)H僅指向一個(gè)對(duì)象,這個(gè)時(shí)候就需要更高級(jí)的工具,比如 guppy , pysizer , pytracemalloc, objgraph 。在這里重點(diǎn)介紹pytracemalloc。

在Python3.4中,已經(jīng)支持了pytracemalloc,如果使用python2.7版本,則需要對(duì)源碼打補(bǔ)丁,然后重新編譯。pytracemalloc在 pep454 中提出,主要有以下幾個(gè)特點(diǎn):

  • Traceback where an object was allocated
  • Statistics on allocated memory blocks per filename and per line number: total size, number and average size of allocated memory blocks
  • Compute the differences between two snapshots to detect memory leaks

簡(jiǎn)單來(lái)說(shuō),pytracemalloc hook住了python申請(qǐng)和釋放內(nèi)存的接口,從而能夠追蹤對(duì)象的分配和回收情況。對(duì)內(nèi)存分配的統(tǒng)計(jì)數(shù)據(jù)可以精確到每個(gè)文件、每一行代碼,也可以按照調(diào)用棧做聚合分析。而且還支持快照(snapshot)功能,比較兩個(gè)快照之間的差異可以發(fā)現(xiàn)潛在的內(nèi)存泄露。

下面通過(guò)一個(gè)例子來(lái)簡(jiǎn)單介紹pytracemalloc的用法和接口,關(guān)于更詳細(xì)用法和API,可以參考這份詳盡的 文檔 或者pytracemalloc的作者在pycon上的 演講ppt 。

import tracemalloc
 
NUM_OF_ATTR =  10
NUM_OF_INSTANCE = 100
 
class Slots(object):
    __slots__ = ['attr%s'%i for i in range(NUM_OF_ATTR)]
    def __init__(self):
        value_lst = (1.0, True, [], {}, ())
        for i in range(NUM_OF_ATTR):
            setattr(self, 'attr%s'%i, value_lst[i % len(value_lst)])
 
 
class NoSlots(object):
    def __init__(self):
        value_lst = (1.0, True, [], {}, ())
        for i in range(NUM_OF_ATTR):
            setattr(self, 'attr%s'%i, value_lst[i % len(value_lst)])
 
 
 
def generate_some_objs():
    lst = []
    for i in range(NUM_OF_INSTANCE):
        o = Slots() if i % 2 else NoSlots()
        lst.append(o)
    return lst
 
 
if __name__ == '__main__':
    tracemalloc.start(3)
 
    t = generate_some_objs() 
 
    snapshot = tracemalloc.take_snapshot()
    top_stats = snapshot.statistics('lineno') # lineno filename traceback
 
    print(tracemalloc.get_traced_memory())
    for stat in top_stats[:10]:
        print(stat)

在上面的代碼中,用到了pytracemalloc幾個(gè)核心的API:

start(nframe: int=1)

pytracemalloc的一大好處就是可以隨時(shí)啟停,start函數(shù)即開(kāi)始追蹤內(nèi)存分配,相應(yīng)的stop會(huì)停止追蹤。start函數(shù)有一個(gè)參數(shù),nframes : 內(nèi)存分配時(shí)記錄的棧的深度,這個(gè)值越大,pytracemalloc本身消耗的內(nèi)存越多,在計(jì)算cumulative數(shù)據(jù)的時(shí)候有用。

    get_traced_memory()

返回值是擁有兩個(gè)元素的tuple,第一個(gè)元素是當(dāng)前分配的內(nèi)存,第二個(gè)元素是自?xún)?nèi)存追蹤啟動(dòng)以來(lái)的內(nèi)存峰值。

take_snapshot()

返回當(dāng)前內(nèi)存分配快照,返回值是Snapshot對(duì)象,該對(duì)象可以按照單個(gè)文件、單行、單個(gè)調(diào)用棧統(tǒng)計(jì)內(nèi)存分配情況

運(yùn)行環(huán)境:windows 64位python3.4

(62280, 62920)
 
test_pytracemalloc_use_py3.4.py:10: size=16.8 KiB, count=144, average=120 B
test_pytracemalloc_use_py3.4.py:17: size=16.7 KiB, count=142, average=120 B
test_pytracemalloc_use_py3.4.py:19: size=9952 B, count=100, average=100 B
test_pytracemalloc_use_py3.4.py:26: size=9792 B, count=102, average=96 B
test_pytracemalloc_use_py3.4.py:27: size=848 B, count=1, average=848 B
test_pytracemalloc_use_py3.4.py:34: size=456 B, count=1, average=456 B
test_pytracemalloc_use_py3.4.py:36: size=448 B, count=1, average=448 B
D:\Python3.4\lib\tracemalloc.py:474: size=64 B, count=1, average=64 B

如果將第36行的“l(fā)ineno“改成“filename”,那么結(jié)果如下

(62136, 62764)
 
test_pytracemalloc_use_py3.4.py:0: size=54.5 KiB, count=491, average=114 B
D:\Python3.4\lib\tracemalloc.py:0: size=64 B, count=1, average=64 B

有了Profile結(jié)果之后,可以看出來(lái)在哪個(gè)文件中有大量的內(nèi)存分配。與性能優(yōu)化相同,造成瓶頸的有兩種情況:?jiǎn)蝹(gè)對(duì)象占用了大量的內(nèi)存;同時(shí)大量存在的小對(duì)象。對(duì)于前者,優(yōu)化的手段并不多,惰性初始化屬性可能有一些幫助;而對(duì)于后者,當(dāng)同樣類(lèi)型的對(duì)象大量存在時(shí),可以使用slots進(jìn)行優(yōu)化。

Slots

默認(rèn)情況下,自定義的對(duì)象都使用dict來(lái)存儲(chǔ)屬性(通過(guò)obj.__dict__查看),而python中的dict大小一般比實(shí)際存儲(chǔ)的元素個(gè)數(shù)要大(以此降低hash沖突概率),因此會(huì)浪費(fèi)一定的空間。在新式類(lèi)中使用__slots__,就是告訴Python虛擬機(jī),這種類(lèi)型的對(duì)象只會(huì)用到這些屬性,因此虛擬機(jī)預(yù)留足夠的空間就行了,如果聲明了__slots__,那么對(duì)象就不會(huì)再有__dict__屬性。

使用slots到底能帶來(lái)多少內(nèi)存優(yōu)化呢,首先看看 這篇文章 ,對(duì)于一個(gè)只有三個(gè)屬性的Image類(lèi),使用__slots__之后內(nèi)存從25.5G下降到16.2G,節(jié)省了9G的空間!

Python 內(nèi)存優(yōu)化

到底能省多少,取決于類(lèi)自身有多少屬性、屬性的類(lèi)型,以及同時(shí)存在多少個(gè)類(lèi)的實(shí)例。下面通過(guò)一段簡(jiǎn)單代碼測(cè)試一下:

# -*- coding: utf-8 -*-
import sys
import tracemalloc
 
NUM_OF_ATTR =  3 #3 # 10 # 30 #90
NUM_OF_INSTANCE = 10 # 10 # 100
 
class Slots(object):
    __slots__ = ['attr%s'%i for i in range(NUM_OF_ATTR)]
    def __init__(self):
        value_lst = (1.0, True, [], {}, ())
        for i in range(NUM_OF_ATTR):
            setattr(self, 'attr%s'%i, value_lst[i % len(value_lst)])
 
 
class NoSlots(object):
    def __init__(self):
        value_lst = (1.0, True, [], {}, ())
        for i in range(NUM_OF_ATTR):
            setattr(self, 'attr%s'%i, value_lst[i % len(value_lst)])
 
if __name__ == '__main__':
    clz = Slots if len(sys.argv) > 1 else NoSlots
    tracemalloc.start()
    objs = [clz() for i in range(NUM_OF_INSTANCE)]
    print(tracemalloc.get_traced_memory()[0])

上面的代碼,主要是在每個(gè)實(shí)例的屬性數(shù)目、并發(fā)存在的實(shí)例數(shù)目?jī)蓚(gè)維度進(jìn)行測(cè)試,并沒(méi)有測(cè)試不同的屬性類(lèi)型。結(jié)果如下表:

Python 內(nèi)存優(yōu)化

百分比為內(nèi)存優(yōu)化百分比,計(jì)算公式為(b – a) / b, 其中b為沒(méi)有使用__slots__時(shí)分配的內(nèi)存, a為使用了__slots__時(shí)分配的內(nèi)存。

注意事項(xiàng)

關(guān)于__slots__,Python文檔有非常詳盡的介紹,這里只強(qiáng)調(diào)幾點(diǎn)注意事項(xiàng)

第一:基類(lèi)和子類(lèi)都必須__slots__,即使基類(lèi)或者子類(lèi)沒(méi)有屬性

>>> class Base(object):
 
...     pass
...
>>> class Derived(Base):
...     __slots__ = ('a', )
...
>>> d.__slots__
('a',)
>>> getattr(d, '__dict__', 'No Dict')
{}

從上面的示例可以看到,子類(lèi)的對(duì)象還是有__dict__屬性,原因就在于基類(lèi)沒(méi)有聲明__slots__。因此,可以通過(guò)看子類(lèi)的實(shí)例有沒(méi)有__dict__屬性來(lái)判斷slots的使用是否正確

第二:子類(lèi)會(huì)繼承基類(lèi)的__slots__

更準(zhǔn)確的說(shuō),如果訪問(wèn)屬性的時(shí)候沒(méi)有在子類(lèi)的__slots__找到,會(huì)繼續(xù)在基類(lèi)的__slots__查找,因?yàn)镻ython使用descriptor在類(lèi)這個(gè)層級(jí)實(shí)現(xiàn)__slots__的,具體可以參見(jiàn)《 python屬性查找 深入理解 》一文

>>> class Base(object):
 
...     __slots__ = ('a',)
...
>>> class Derived(Base):
...     __slots__ = ('b', )
...
>>> d = Derived()
>>> d.__slots__
('b',)
>>> getattr(d, '__dict__', 'No Dict')
'No Dict'
>>> d.a = 1
>>> d.c = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Derived' object has no attribute 'c'

objgraph

在大型工程中,怎么排查有哪些大量存在的對(duì)象呢,畢竟同一個(gè)類(lèi)型存在的對(duì)象越多,優(yōu)化越有效果。除了直接看代碼,最好使的就是使用objgraph.py的show_most_common_types(N)函數(shù),該函數(shù)返回Python gc管理的所有對(duì)象中,數(shù)目前N多的對(duì)象,在排除掉python builtin對(duì)象之后,剩下的就是可優(yōu)化的對(duì)象。比如在最上面的代碼中:在最后加上這么兩句:

import objgraph
 
objgraph.show_most_common_types(25)

輸出如下:

Python 內(nèi)存優(yōu)化

再論P(yáng)ython dict

前面介紹slots的時(shí)候,就提到Python自定義的對(duì)象中通過(guò)dict來(lái)管理屬性。這種機(jī)制極大的提高了Python的靈活性 — 可以隨時(shí)給對(duì)象增加屬性,但是其實(shí)現(xiàn)機(jī)制也帶來(lái)了內(nèi)存上的浪費(fèi)。不管是python源碼,還是Python程序,都大量使用了dict,因此這部分內(nèi)存浪費(fèi)不容小視。

python中的dict使用的是散列表(類(lèi)似C++中的std::unordered_map),當(dāng)計(jì)算出的hash值沖突的時(shí)候,采用開(kāi)放地址法解決沖突(另一種常見(jiàn)的沖突解決算法是鏈表法)。為了降低沖突概率,當(dāng)裝填因子(實(shí)際存儲(chǔ)的元素與散列表長(zhǎng)度的比值)超過(guò)2/3的時(shí)候就會(huì)對(duì)散列表進(jìn)行擴(kuò)容,因此散列表中一定會(huì)存在一些未使用的槽。

下面簡(jiǎn)單看看PyDictObject的數(shù)據(jù)結(jié)構(gòu)(python2.7.3 dictobject.h)

#define PyDict_MINSIZE 8
 
typedef struct {
    /* Cached hash code of me_key.  Note that hash codes are C longs.
     * We have to use Py_ssize_t instead because dict_popitem() abuses
     * me_hash to hold a search finger.
     */
    Py_ssize_t me_hash;
    PyObject *me_key;
    PyObject *me_value;
} PyDictEntry;
 
 
typedef struct _dictobject PyDictObject;
struct _dictobject {
    PyObject_HEAD
    Py_ssize_t ma_fill;  /* # Active + # Dummy */
    Py_ssize_t ma_used;  /* # Active */
 
    /* The table contains ma_mask + 1 slots, and that's a power of 2.
     * We store the mask instead of the size because the mask is more
     * frequently needed.
     */
    Py_ssize_t ma_mask;
 
    /* ma_table points to ma_smalltable for small tables, else to
     * additional malloc'ed memory.  ma_table is never NULL!  This rule
     * saves repeated runtime null-tests in the workhorse getitem and
     * setitem calls.
     */
    PyDictEntry *ma_table;
    PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);
    PyDictEntry ma_smalltable[PyDict_MINSIZE];
};

從定義可以看出,除了固定的部分(幾個(gè)Py_ssize_t),PyDictObject中主要是PyDictEntry對(duì)象,PyDictEntrty包含一個(gè)Py_ssize_t(int)和兩個(gè)指針。上面源碼中的注釋?zhuān)ǖ?6行)指出,當(dāng)dict的元素比較少時(shí),ma_table指向ma_smalltable,當(dāng)元素增多時(shí),ma_table會(huì)指向新申請(qǐng)的空間。ma_smalltable的作用在于Python(不管是源碼還是代碼)都大量使用dict,一般來(lái)說(shuō),存儲(chǔ)的元素也不會(huì)太多,因此Python就先開(kāi)辟好PyDict_MINSIZE(默認(rèn)為8)個(gè)空間。

為什么說(shuō)PyDictObject存在浪費(fèi)呢,PyDictEntry在32位下也有12個(gè)字節(jié),那么即使在ma_smalltable(ma_table)中大量的位置沒(méi)有被使用時(shí),也要占用這么多字節(jié)。用 這篇文章 中的例子:

假設(shè)有這么一個(gè)dict:

    d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

在Python源碼中的視圖就是這樣的:

    # 下面的entries就是ma_smalltable
 
entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

然而,完全可以這么存儲(chǔ):

indices = [None, 1, None, None, None, 0, None, 2]
 
entries =  [[-9092791511155847987, 'timmy', 'red'],
                [-8522787127447073495, 'barry', 'green'],
                [-6480567542315338377, 'guido', 'blue']]

indices的作用類(lèi)似ma_smalltable,但只存儲(chǔ)一個(gè)數(shù)組的索引值,數(shù)組只存儲(chǔ)實(shí)際存在的元素(PyDictEntry),當(dāng)dict中的元素越稀疏,相比上一種存儲(chǔ)方式使用的內(nèi)存越少。而且,這種實(shí)現(xiàn), dict就是有序的(按插入時(shí)間排序)

這就是python3.6中新的dict實(shí)現(xiàn),Compact dict! Stackoverflow上也有相關(guān) 討論 。

總結(jié)

本文中介紹了Python內(nèi)存優(yōu)化的Profile工具,最有效的優(yōu)化方法:使用slots,也介紹了在python3.6中新的dict實(shí)現(xiàn)。

當(dāng)然,還有一些良好的編碼習(xí)慣。比如盡量使用immutable而不是mutable對(duì)象:使用tuple而不是list,使用frozenset而不是set;另外,就是盡量使用迭代器,比如python2.7中,使用xrange而不是range,dict的iterxx版本。

 

來(lái)自: http://python.jobbole.com/88896/

 

標(biāo)簽: linux 代碼 移動(dòng)平臺(tái)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:iOS藍(lán)牙開(kāi)發(fā)CoreBluetooth框架總結(jié)

下一篇:Python并行處理