日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

python垃圾回收价格表_深度解析Python垃圾回收机制(超级详细)

發(fā)布時(shí)間:2024/1/23 python 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python垃圾回收价格表_深度解析Python垃圾回收机制(超级详细) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

我們知道,目前的計(jì)算機(jī)都采用的是圖靈機(jī)架構(gòu),其本質(zhì)就是用一條無限長的紙帶,對應(yīng)今天的存儲(chǔ)器。隨后在工程學(xué)的推演中,逐漸出現(xiàn)了寄存器、易失性存儲(chǔ)器(內(nèi)存)以及永久性存儲(chǔ)器(硬盤)等產(chǎn)品。由于不同的存儲(chǔ)器,其速度越快,單位價(jià)格也就越昂貴,因此,妥善利用好每一寸告訴存儲(chǔ)器的空間,永遠(yuǎn)是系統(tǒng)設(shè)計(jì)的一個(gè)核心。

Python?程序在運(yùn)行時(shí),需要在內(nèi)存中開辟出一塊空間,用于存放運(yùn)行時(shí)產(chǎn)生的臨時(shí)變量,計(jì)算完成后,再將結(jié)果輸出到永久性存儲(chǔ)器中。但是當(dāng)數(shù)據(jù)量過大,或者內(nèi)存空間管理不善,就很容易出現(xiàn)內(nèi)存溢出的情況,程序可能會(huì)被操作系統(tǒng)終止。

而對于服務(wù)器這種用于永不中斷的系統(tǒng)來說,內(nèi)存管理就顯得更為重要了,不然很容易引發(fā)內(nèi)存泄漏。

這里的內(nèi)存泄漏是指程序本身沒有設(shè)計(jì)好,導(dǎo)致程序未能釋放已不再使用的內(nèi)存,或者直接失去了對某段內(nèi)存的控制,造成了內(nèi)存的浪費(fèi)。

那么,對于不會(huì)再用到的內(nèi)存空間,Python 是通過什么機(jī)制來管理的呢?其實(shí)在前面章節(jié)已大致接觸過,就是引用計(jì)數(shù)機(jī)制。

Python引用計(jì)數(shù)機(jī)制

在學(xué)習(xí) Python 的整個(gè)過程中,我們一直在強(qiáng)調(diào),Python 中一切皆對象,也就是說,在 Python 中你用到的一切變量,本質(zhì)上都是類對象。

那么,如何知道一個(gè)對象永遠(yuǎn)都不能再使用了呢?很簡單,就是當(dāng)這個(gè)對象的引用計(jì)數(shù)值為 0 時(shí),說明這個(gè)對象永不再用,自然它就變成了垃圾,需要被回收。

舉個(gè)例子:

import os

import psutil

# 顯示當(dāng)前 python 程序占用的內(nèi)存大小

def show_memory_info(hint):

pid = os.getpid()

p = psutil.Process(pid)

info = p.memory_full_info()

memory = info.uss / 1024. / 1024

print('{} memory used: {} MB'.format(hint, memory))

def func():

show_memory_info('initial')

a = [i for i in range(10000000)]

show_memory_info('after a created')

func()

show_memory_info('finished')

輸出結(jié)果為:

initial memory used: 47.19140625 MB

after a created memory used: 433.91015625 MB

finished memory used: 48.109375 MB

注意,運(yùn)行此程序之前,需安裝 psutil 模塊(獲取系統(tǒng)信息的模塊),可使用 pip 命令直接安裝,執(zhí)行命令為 $pip install psutil,如果遇到 Permission denied 安裝失敗,請加上 sudo 重試。

可以看到,當(dāng)調(diào)用函數(shù) func() 且列表 a 被創(chuàng)建之后,內(nèi)存占用迅速增加到了 433 MB,而在函數(shù)調(diào)用結(jié)束后,內(nèi)存則返回正常。這是因?yàn)?#xff0c;函數(shù)內(nèi)部聲明的列表 a 是局部變量,在函數(shù)返回后,局部變量的引用會(huì)注銷掉,此時(shí)列表 a 所指代對象的引用計(jì)數(shù)為 0,Python 便會(huì)執(zhí)行垃圾回收,因此之前占用的大量內(nèi)存就又回來了。

明白了這個(gè)原理后,稍微修改上面的代碼,如下所示:

def func():

show_memory_info('initial')

global a

a = [i for i in range(10000000)]

show_memory_info('after a created')

func()

show_memory_info('finished')

輸出結(jié)果為:

initial memory used: 48.88671875 MB

after a created memory used: 433.94921875 MB

finished memory used: 433.94921875 MB

上面這段代碼中,global a 表示將 a 聲明為全局變量,則即使函數(shù)返回后,列表的引用依然存在,于是 a 對象就不會(huì)被當(dāng)做垃圾回收掉,依然占用大量內(nèi)存。

同樣,如果把生成的列表返回,然后在主程序中接收,那么引用依然存在,垃圾回收也不會(huì)被觸發(fā),大量內(nèi)存仍然被占用著:

def func():

show_memory_info('initial')

a = [i for i in derange(10000000)]

show_memory_info('after a created')

return a

a = func()

show_memory_info('finished')

輸出結(jié)果為:

initial memory used: 47.96484375 MB

after a created memory used: 434.515625 MB

finished memory used: 434.515625 MB

以上最常見的幾種情況,下面由表及里,深入看一下 Python 內(nèi)部的引用計(jì)數(shù)機(jī)制。先來分析一段代碼:

import sys

a = []

# 兩次引用,一次來自 a,一次來自 getrefcount

print(sys.getrefcount(a))

def func(a):

# 四次引用,a,python 的函數(shù)調(diào)用棧,函數(shù)參數(shù),和 getrefcount

print(sys.getrefcount(a))

func(a)

# 兩次引用,一次來自 a,一次來自 getrefcount,函數(shù) func 調(diào)用已經(jīng)不存在

print(sys.getrefcount(a))

輸出結(jié)果為:

2

4

2

注意,sys.getrefcount() 函數(shù)用于查看一個(gè)變量的引用次數(shù),不過別忘了,getrefcount 本身也會(huì)引入一次計(jì)數(shù)。

另一個(gè)要注意的是,在函數(shù)調(diào)用發(fā)生的時(shí)候,會(huì)產(chǎn)生額外的兩次引用,一次來自函數(shù)棧,另一個(gè)是函數(shù)參數(shù)。

import sys

a = []

print(sys.getrefcount(a)) # 兩次

b = a

print(sys.getrefcount(a)) # 三次

c = b

d = b

e = c

f = e

g = d

print(sys.getrefcount(a)) # 八次

輸出結(jié)果為:

2

3

8

分析一下這段代碼,a、b、c、d、e、f、g 這些變量全部指代的是同一個(gè)對象,而 sys.getrefcount() 函數(shù)并不是統(tǒng)計(jì)一個(gè)指針,而是要統(tǒng)計(jì)一個(gè)對象被引用的次數(shù),所以最后一共會(huì)有 8 次引用。

理解引用這個(gè)概念后,引用釋放是一種非常自然和清晰的思想。相比 C 語言中需要使用 free 去手動(dòng)釋放內(nèi)存,Python 的垃圾回收在這里可以說是省心省力了。

不過,有讀者還是會(huì)好奇,如果想手動(dòng)釋放內(nèi)存,應(yīng)該怎么做呢?方法同樣很簡單,只需要先調(diào)用 del a 來刪除一個(gè)對象,然后強(qiáng)制調(diào)用 gc.collect() 即可手動(dòng)啟動(dòng)垃圾回收。例如:

import gc

show_memory_info('initial')

a = [i for i in range(10000000)]

show_memory_info('after a created')

del a

gc.collect()

show_memory_info('finish')

print(a)

輸出結(jié)果為:

initial memory used: 48.1015625 MB

after a created memory used: 434.3828125 MB

finish memory used: 48.33203125 MB

NameError Traceback (most recent call last)

in

11

12 show_memory_info('finish')

---> 13 print(a)

NameError: name 'a' is not defined

是不是覺得垃圾回收非常簡單呢?這里再問大家一個(gè)問題:引用次數(shù)為 0 是垃圾回收啟動(dòng)的充要條件嗎?還有沒有其他可能性呢?

其實(shí),引用計(jì)數(shù)是其中最簡單的實(shí)現(xiàn),引用計(jì)數(shù)并非充要條件,它只能算作充分非必要條件,至于其他的可能性,下面所講的循環(huán)引用正是其中一種。

循環(huán)引用

首先思考一個(gè)問題,如果有兩個(gè)對象,之間互相引用,且不再被別的對象所引用,那么它們應(yīng)該被垃圾回收嗎?

舉個(gè)例子:

def func():

show_memory_info('initial')

a = [i for i in range(10000000)]

b = [i for i in range(10000000)]

show_memory_info('after a, b created')

a.append(b)

b.append(a)

func()

show_memory_info('finished')

輸出結(jié)果為:

initial memory used: 47.984375 MB

after a, b created memory used: 822.73828125 MB

finished memory used: 821.73046875 MB

程序中,a 和 b 互相引用,并且作為局部變量在函數(shù) func 調(diào)用結(jié)束后,a 和 b 這兩個(gè)指針從程序意義上已經(jīng)不存在,但從輸出結(jié)果中看到,依然有內(nèi)存占用,這是為什么呢?因?yàn)榛ハ嘁脤?dǎo)致它們的引用數(shù)都不為 0。

試想一下,如果這段代碼出現(xiàn)在生產(chǎn)環(huán)境中,哪怕 a 和 b 一開始占用的空間不是很大,但經(jīng)過長時(shí)間運(yùn)行后,Python 所占用的內(nèi)存一定會(huì)變得越來越大,最終撐爆服務(wù)器,后果不堪設(shè)想。

有讀者可能會(huì)說,互相引用還是很容易被發(fā)現(xiàn)的呀,問題不大。可是,更隱蔽的情況是出現(xiàn)一個(gè)引用環(huán),在工程代碼比較復(fù)雜的情況下,引用環(huán)真不一定能被輕易發(fā)現(xiàn)。那么應(yīng)該怎么做呢?

事實(shí)上,Python 本身能夠處理這種情況,前面剛剛講過,可以顯式調(diào)用 gc.collect() 來啟動(dòng)垃圾回收,例如:

import gc

def func():

show_memory_info('initial')

a = [i for i in range(10000000)]

b = [i for i in range(10000000)]

show_memory_info('after a, b created')

a.append(b)

b.append(a)

func()

gc.collect()

show_memory_info('finished')

輸出結(jié)果為:

initial memory used: 49.51171875 MB

after a, b created memory used: 824.1328125 MB

finished memory used: 49.98046875 MB

事實(shí)上,Python 使用標(biāo)記清除(mark-sweep)算法和分代收集(generational),來啟用針對循環(huán)引用的自動(dòng)垃圾回收。

先來看標(biāo)記清除算法。我們先用圖論來理解不可達(dá)的概念。對于一個(gè)有向圖,如果從一個(gè)節(jié)點(diǎn)出發(fā)進(jìn)行遍歷,并標(biāo)記其經(jīng)過的所有節(jié)點(diǎn);那么,在遍歷結(jié)束后,所有沒有被標(biāo)記的節(jié)點(diǎn),我們就稱之為不可達(dá)節(jié)點(diǎn)。顯而易見,這些節(jié)點(diǎn)的存在是沒有任何意義的,自然的,我們就需要對它們進(jìn)行垃圾回收。

當(dāng)然,每次都遍歷全圖,對于 Python 而言是一種巨大的性能浪費(fèi)。所以,在 Python 的垃圾回收實(shí)現(xiàn)中,標(biāo)記清除算法使用雙向鏈表維護(hù)了一個(gè)數(shù)據(jù)結(jié)構(gòu),并且只考慮容器類的對象(只有容器類對象才有可能產(chǎn)生循環(huán)引用)。

而分代收集算法,則是將 Python 中的所有對象分為三代。剛剛創(chuàng)立的對象是第 0 代;經(jīng)過一次垃圾回收后,依然存在的對象,便會(huì)依次從上一代挪到下一代。而每一代啟動(dòng)自動(dòng)垃圾回收的閾值,則是可以單獨(dú)指定的。當(dāng)垃圾回收器中新增對象減去刪除對象達(dá)到相應(yīng)的閾值時(shí),就會(huì)對這一代對象啟動(dòng)垃圾回收。

事實(shí)上,分代收集基于的思想是,新生的對象更有可能被垃圾回收,而存活更久的對象也有更高的概率繼續(xù)存活。因此,通過這種做法,可以節(jié)約不少計(jì)算量,從而提高 Python 的性能。

總結(jié)

以上是生活随笔為你收集整理的python垃圾回收价格表_深度解析Python垃圾回收机制(超级详细)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。