小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

python變量的內(nèi)存機(jī)制

 南校區(qū)于瀟瀟 2019-08-09

python變量的內(nèi)存機(jī)制

作為一門簡(jiǎn)單易用的語(yǔ)言,且配備海量的庫(kù),python可謂是程序員手中的掌中寶,編程本身就是一種將人類思維轉(zhuǎn)化為計(jì)算機(jī)思維的技術(shù),如果不需要去追求極致的運(yùn)行效率同時(shí)又不限制于計(jì)算機(jī)內(nèi)存空間,python無(wú)疑是目前最方便的語(yǔ)言了。

作為一個(gè)合格的程序員,自然是要知其然并知其所以然,除了能夠應(yīng)用python來(lái)放飛自我之外,同時(shí)也要探究python其內(nèi)部的運(yùn)行原理,首當(dāng)其沖的python編程中必須要用到的變量以及背后的運(yùn)行機(jī)制。

注:以下示例在linux平臺(tái)下編寫,使用python2.7

引用機(jī)制

python的變量-內(nèi)存模型更像是C++中的引用機(jī)制,python中的每個(gè)變量不一定占用內(nèi)存空間,變量更像是一份內(nèi)存的引用,通過這個(gè)變量可以訪問到內(nèi)存中的數(shù)據(jù),舉個(gè)例子:

>>>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))

輸出結(jié)果:

b51080.b510807f28bf69b758.7f28bf69b758  

其中id()是python的系統(tǒng)函數(shù),返回對(duì)象的內(nèi)存起始地址。

從結(jié)果可以看出,a與b,c與d變量對(duì)應(yīng)的地址事實(shí)上為同一個(gè)地址,也就是當(dāng)我們使用變量a和b時(shí),使用的是同一個(gè)對(duì)象,而a,b是這個(gè)對(duì)象的引用,我們可以通過系統(tǒng)函數(shù)sys.getrefcount()來(lái)查看一個(gè)對(duì)象的引用數(shù)量:

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

輸出結(jié)果:

23

顯然,這個(gè)結(jié)果并不在我們的預(yù)料當(dāng)中,由于a和b在同一個(gè)地址,結(jié)果應(yīng)該是1、2,為什么是2,3呢?

這是因?yàn)樵趕ys.getrefcount()函數(shù)調(diào)用時(shí),a作為參數(shù)也被引用了一次,所以出現(xiàn)了2、3的結(jié)果。

緩存小數(shù)據(jù)機(jī)制

上面講了python變量賦值時(shí)的內(nèi)存機(jī)制,事情就這么完美結(jié)束了嗎?

并沒有?。。?/p>

我們?cè)賮?lái)看一個(gè)例子:

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

輸出結(jié)果:

b51080.b51080

看到這個(gè)結(jié)果,我緩緩摘下我的眼鏡,拿95%濃度的醫(yī)用酒精仔仔細(xì)細(xì)擦了三遍之后再戴上看,沒看錯(cuò)!這兩個(gè)變量還是同一個(gè)地址內(nèi)容的引用,這一次兩個(gè)變量的初始化是獨(dú)立的,并非賦值初始化,為什么兩個(gè)變量還是同一個(gè)地址的引用呢?

答案是:

在Python中,Python會(huì)有一個(gè)緩存對(duì)象的機(jī)制,以便重復(fù)使用。當(dāng)我們創(chuàng)建多個(gè)等于1的引用時(shí),實(shí)際上是讓所有這些引用指向同一個(gè)對(duì)象,以達(dá)到節(jié)省資源的目的

原來(lái)是這樣!?。?/p>

但是仔細(xì)一想,這不對(duì)吧?如果每個(gè)數(shù)據(jù)都進(jìn)行緩存,那豈不是對(duì)內(nèi)存空間的極度浪費(fèi)?還是說(shuō)內(nèi)存回收機(jī)制會(huì)過一段時(shí)間回收一次垃圾內(nèi)存?
我們?cè)賮?lái)看下面一個(gè)例子:

>>> 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))

輸出結(jié)果:

5223836.52238365225932.52259325241840.5241864

從結(jié)果來(lái)看,當(dāng)a小于256時(shí),這個(gè)值會(huì)被系統(tǒng)緩存循環(huán)利用,而當(dāng)a>256時(shí),系統(tǒng)并不會(huì)進(jìn)行緩存(當(dāng)然不僅僅是三次實(shí)驗(yàn)的結(jié)果,博主后續(xù)還試了很多值,就不一一列出了)

我們來(lái)用另一種方法來(lái)驗(yàn)證這個(gè)問題,即sys.getrefcount():

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

輸出結(jié)果為:

152

結(jié)果顯而易見,10這個(gè)值被系統(tǒng)緩存,且在別處引用了多次,而257這個(gè)值為2(為什么為2而不是1在上面有解釋)

那么問題又來(lái)了,如果是其他類型的數(shù)據(jù)呢?我們接著看

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

結(jié)果為:

39422528.39422528

短字符串也會(huì)有緩存機(jī)制

然后是list:

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

list并沒有緩存機(jī)制,從這里可以看出,python的緩存機(jī)制并不針對(duì)所有變量類型

變量緩存結(jié)論

根據(jù)各種實(shí)驗(yàn)以及多方查證,結(jié)果表明:

  • python的變量其實(shí)是一種堆內(nèi)存的引用,可以理解為一個(gè)實(shí)體的標(biāo)簽,而在不同變量之間的拷貝復(fù)制(如a=b),他們所表示的對(duì)象實(shí)體是同一個(gè)
  • python會(huì)對(duì)-5-256(包括256)的整型數(shù)據(jù)和短字符串進(jìn)行緩存以節(jié)省多次分配銷毀的開銷

看到這里,喜歡思考的朋友們不禁就要問了,緩存這些整型數(shù)據(jù)和短字符串真的對(duì)性能有明顯提升嗎?python代碼中能有多少個(gè)整型變量?

答案是:整型變量對(duì)應(yīng)整型的內(nèi)存對(duì)象,但是整型的內(nèi)存對(duì)象并不僅僅對(duì)應(yīng)整型的變量類型,容器中的整形元素可能也是整形變量的引用

如果你還有疑惑,我們來(lái)看看下面的例子:

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

從打印的結(jié)果可以看出,整型變量a=1,表示a指向?qū)ο?,為1的引用,b[0]也被初始化為1,同樣的,b[0]同時(shí)也是對(duì)象1的引用,對(duì)于所有容器而言,都是這種形式,看到這里,各位觀眾老爺們應(yīng)該是有所理解了吧。

關(guān)于python變量?jī)?nèi)存機(jī)制對(duì)變量使用的影響可以參考這一篇博客:python函數(shù)調(diào)用時(shí)參數(shù)傳遞方式

內(nèi)存回收

既然說(shuō)到了內(nèi)存機(jī)制,必然涉及到分配和回收的機(jī)制,內(nèi)存分配就很簡(jiǎn)單,在定義對(duì)象的時(shí)候用到進(jìn)行內(nèi)存的分配,而內(nèi)存的回收則沒那么簡(jiǎn)單,因?yàn)樵趦?nèi)存回收的過程中,python無(wú)法執(zhí)行其他任務(wù),所以頻繁地內(nèi)存回收會(huì)導(dǎo)致嚴(yán)重的效率問題,而內(nèi)存回收間隔時(shí)間過長(zhǎng)則會(huì)導(dǎo)致內(nèi)存浪費(fèi)嚴(yán)重,所以一般只有在特定時(shí)間內(nèi)啟動(dòng)內(nèi)存回收。

python運(yùn)行時(shí),會(huì)記錄下來(lái)分配和釋放的次數(shù),只有當(dāng)兩個(gè)值的差大于某個(gè)數(shù)值時(shí),即

分配次數(shù)-釋放次數(shù)>觸發(fā)回收的閾值

時(shí),python進(jìn)行垃圾回收,我們可以使用get_threshold()方法來(lái)獲取閾值:

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

輸出結(jié)果:

(700,10,10)

這個(gè)700便是觸發(fā)內(nèi)存回收的閾值。但是后面的兩個(gè)10又是什么意思呢?

這也是內(nèi)存回收中的一種機(jī)制,叫做分代回收,這一策略的基本假設(shè)是:存在時(shí)間越久的對(duì)象,越不可能成為垃圾對(duì)象,即給予一些長(zhǎng)期使用的對(duì)象更多信任。

Python將所有的對(duì)象分為0,1,2三代。所有的新建對(duì)象都是0代對(duì)象。當(dāng)某一代對(duì)象經(jīng)歷過垃圾回收,依然存活,那么它就被歸入下一代對(duì)象。垃圾回收啟動(dòng)時(shí),一定會(huì)掃描所有的0代對(duì)象。如果0代經(jīng)過一定次數(shù)垃圾回收,那么就啟動(dòng)對(duì)0代和1代的掃描清理。當(dāng)1代也經(jīng)歷了一定次數(shù)的垃圾回收后,那么會(huì)啟動(dòng)對(duì)0,1,2,即對(duì)所有對(duì)象進(jìn)行掃描

這兩個(gè)次數(shù)即上面get_threshold()返回的(700, 10, 10)返回的兩個(gè)10。也就是說(shuō),每10次0代垃圾回收,會(huì)配合1次1代的垃圾回收;而每10次1代的垃圾回收,才會(huì)有1次的2代垃圾回收。

我們也可以手動(dòng)地調(diào)整觸發(fā)回收的閾值,聰明的朋友們可以猜到這個(gè)方法了,既然有g(shù)et,必然相對(duì)應(yīng)的就是set:

import gcgc.set_threshold(600,8,7)

除了被動(dòng)地等待系統(tǒng)回收,當(dāng)然也可以手動(dòng)地進(jìn)行內(nèi)存回收:

import gcgc.collect()  

其實(shí)java也好,python也好,每一種語(yǔ)言的內(nèi)存機(jī)制將從根本上影響語(yǔ)言的執(zhí)行效率,所以在內(nèi)存的處理上會(huì)有很多更加復(fù)雜的細(xì)節(jié),這里只是介紹了一個(gè)大體的框架,班門弄斧,歡迎路過的大神們指正和補(bǔ)充。

好了,關(guān)于python變量?jī)?nèi)存機(jī)制的問題就到此為止了,如果朋友們對(duì)于這個(gè)有什么疑問或者發(fā)現(xiàn)有文章中有什么錯(cuò)誤,歡迎留言

個(gè)人郵箱:linux_downey@sina.com
原創(chuàng)博客,轉(zhuǎn)載請(qǐng)注明出處!

祝各位早日實(shí)現(xiàn)項(xiàng)目叢中過,bug不沾身.
(完)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多