python變量的內存機制

python變量的內存機制

做爲一門簡單易用的語言,且配備海量的庫,python可謂是程序員手中的掌中寶,編程自己就是一種將人類思惟轉化爲計算機思惟的技術,若是不須要去追求極致的運行效率同時又不限制於計算機內存空間,python無疑是目前最方便的語言了。html

做爲一個合格的程序員,天然是要知其然並知其因此然,除了可以應用python來放飛自我以外,同時也要探究python其內部的運行原理,首當其衝的python編程中必需要用到的變量以及背後的運行機制。java

注:如下示例在linux平臺下編寫,使用python2.7python

引用機制

python的變量-內存模型更像是C++中的引用機制,python中的每一個變量不必定佔用內存空間,變量更像是一分內存的引用,經過這個變量能夠訪問到內存中的數據,舉個例子:linux

>>>a=10
>>>b=a
>>>c=[1,2,3,4]
>>>d=c
>>>print "%x%x"  %(id(a),id(b))
>>>print "%x%x"  %(id(c),id(d))

輸出結果:程序員

b51080.b51080
7f28bf69b758.7f28bf69b758

其中id()是python的系統函數,返回對象的內存起始地址。編程

從結果能夠看出,a與b,c與d變量對應的地址事實上爲同一個地址,也就是當咱們使用變量a和b時,使用的是同一個對象,而a,b是這個對象的引用,咱們能夠經過系統函數sys.getrefcount()來查看一個對象的引用數量:緩存

>>>import sys
>>>a=257
>>>print sys.getrefcount(a)
>>>b=a
>>>print sys.getrefcount(a)

輸出結果:框架

2
3

顯然,這個結果並不在咱們的預料當中,因爲a和b在同一個地址,結果應該是一、2,爲何是2,3呢?python2.7

這是由於在sys.getrefcount()函數調用時,a做爲參數也被引用了一次,因此出現了二、3的結果。函數

緩存小數據機制

上面講了python變量賦值時的內存機制,事情就這麼完美結束了嗎?

並無!!!

咱們再來看一個例子:

>>> a=10
>>> b=10
>>> print "%x.%x" %(id(a),id(b))

輸出結果:

b51080.b51080

看到這個結果,我緩緩摘下個人眼鏡,拿95%濃度的醫用酒精仔仔細細擦了三遍以後再戴上看,沒看錯!這兩個變量仍是同一個地址內容的引用,這一次兩個變量的初始化是獨立的,並不是賦值初始化,爲何兩個變量仍是同一個地址的引用呢?

答案是:

在Python中,Python會有一個緩存對象的機制,以便重複使用。當咱們建立多個等於1的引用時,其實是讓全部這些引用指向同一個對象,以達到節省資源的目的

原來是這樣!!!

可是仔細一想,這不對吧?若是每一個數據都進行緩存,那豈不是對內存空間的極度浪費?仍是說內存回收機制會過一段時間回收一次垃圾內存?
咱們再來看下面一個例子:

>>> a=100
>>> b=100
>>> print "%d%d" %(id(a),id(b))
>>> a=256
>>> b=256
>>> print "%d%d" %(id(a),id(b))
>>> a=257 
>>> b=257
>>> print "%d%d" %(id(a),id(b))

輸出結果:

5223836.5223836
5225932.5225932
5241840.5241864

從結果來看,當a小於256時,這個值會被系統緩存循環利用,而當a>256時,系統並不會進行緩存(固然不只僅是三次實驗的結果,博主後續還試了不少值,就不一一列出了)

咱們來用另外一種方法來驗證這個問題,即sys.getrefcount():

>>>import sys
>>>a=10
>>>print sys.getrefcount(a)
>>>a=257
>>>print sys.getrefcount(a)

輸出結果爲:

15
2

結果顯而易見,10這個值被系統緩存,且在別處引用了屢次,而257這個值爲2(爲何爲2而不是1在上面有解釋)

那麼問題又來了,若是是其餘類型的數據呢?咱們接着看

>>>a="downey"
>>>b="downey"
>>>print "%d%d" %(id(a),id(b))

結果爲:

39422528.39422528

短字符串也會有緩存機制

而後是list:

>>>a=[1,2,3]
>>>b=[1,2,3]
>>>print "%d%d" %(id(a),id(b))
39704576.39745176

list並無緩存機制,從這裏能夠看出,python的緩存機制並不針對全部變量類型

變量緩存結論

根據各類實驗以及多方查證,結果代表:

  • python的變量實際上是一種堆內存的引用,能夠理解爲一個實體的標籤,而在不一樣變量之間的拷貝複製(如a=b),他們所表示的對象實體是同一個
  • python會對-5-256(包括256)的整型數據和短字符串進行緩存以節省屢次分配銷燬的開銷

看到這裏,喜歡思考的朋友們不由就要問了,緩存這些整型數據和短字符串真的對性能有明顯提高嗎?python代碼中能有多少個整型變量?

答案是:整型變量對應整型的內存對象,可是整型的內存對象並不只僅對應整型的變量類型,容器中的整形元素可能也是整形變量的引用

若是你還有疑惑,咱們來看看下面的例子:

>>> import sys
>>> a=1
>>> sys.getrefcount(a)
128
>>> b=[1,2,3]
>>> sys.getrefcount(a)
129

從打印的結果能夠看出,整型變量a=1,表示a指向對象1,爲1的引用,b[0]也被初始化爲1,一樣的,b[0]同時也是對象1的引用,對於全部容器而言,都是這種形式,看到這裏,各位觀衆老爺們應該是有所理解了吧。

關於python變量內存機制對變量使用的影響能夠參考這一篇博客:python函數調用時參數傳遞方式

內存回收

既然說到了內存機制,必然涉及到分配和回收的機制,內存分配就很簡單,在定義對象的時候用到進行內存的分配,而內存的回收則沒那麼簡單,由於在內存回收的過程當中,python沒法執行其餘任務,因此頻繁地內存回收會致使嚴重的效率問題,而內存回收間隔時間過長則會致使內存浪費嚴重,因此通常只有在特定時間內啓動內存回收。

python運行時,會記錄下來分配和釋放的次數,只有當兩個值的差大於某個數值時,即

分配次數-釋放次數>觸發回收的閾值

時,python進行垃圾回收,咱們可使用get_threshold()方法來獲取閾值:

>>>import gc
>>>print gc.get_threshold()

輸出結果:

(700,10,10)

這個700即是觸發內存回收的閾值。可是後面的兩個10又是什麼意思呢?

這也是內存回收中的一種機制,叫作分代回收,這一策略的基本假設是:存在時間越久的對象,越不可能成爲垃圾對象,即給予一些長期使用的對象更多信任。

Python將全部的對象分爲0,1,2三代。全部的新建對象都是0代對象。當某一代對象經歷過垃圾回收,依然存活,那麼它就被納入下一代對象。垃圾回收啓動時,必定會掃描全部的0代對象。若是0代通過必定次數垃圾回收,那麼就啓動對0代和1代的掃描清理。當1代也經歷了必定次數的垃圾回收後,那麼會啓動對0,1,2,即對全部對象進行掃描

這兩個次數即上面get_threshold()返回的(700, 10, 10)返回的兩個10。也就是說,每10次0代垃圾回收,會配合1次1代的垃圾回收;而每10次1代的垃圾回收,纔會有1次的2代垃圾回收。

咱們也能夠手動地調整觸發回收的閾值,聰明的朋友們能夠猜到這個方法了,既然有get,必然相對應的就是set:

import gc
gc.set_threshold(600,8,7)

除了被動地等待系統回收,固然也能夠手動地進行內存回收:

import gc
gc.collect()

其實java也好,python也好,每一種語言的內存機制將從根本上影響語言的執行效率,因此在內存的處理上會有不少更加複雜的細節,這裏只是介紹了一個大致的框架,班門弄斧,歡迎路過的大神們指正和補充。

好了,關於python變量內存機制的問題就到此爲止了,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言

我的郵箱:linux_downey@sina.com 原創博客,轉載請註明出處!

祝各位早日實現項目叢中過,bug不沾身. (完)

相關文章
相關標籤/搜索