網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
Python列表創(chuàng)建與銷毀及緩存池機(jī)制_python
作者:??編程學(xué)習(xí)網(wǎng)???? ? 更新時(shí)間: 2022-07-06 編程語(yǔ)言列表的創(chuàng)建
創(chuàng)建列表,Python底層只提供了唯一一個(gè)Python/C API,也就是PyList_New。這個(gè)函數(shù)接收一個(gè)size參數(shù),允許我們?cè)趧?chuàng)建一個(gè)PyListObject對(duì)象時(shí)指定底層的PyObject *數(shù)組的長(zhǎng)度。
PyObject *
PyList_New(Py_ssize_t size)
{
//聲明一個(gè)PyListObject *對(duì)象
PyListObject *op;
#ifdef SHOW_ALLOC_COUNT
static int initialized = 0;
if (!initialized) {
Py_AtExit(show_alloc);
initialized = 1;
}
#endif
//如果size小于0,直接拋異常
if (size < 0) {
PyErr_BadInternalCall();
return NULL;
}
//緩存池是否可用,如果可用
if (numfree) {
//將緩存池內(nèi)對(duì)象個(gè)數(shù)減1
numfree--;
//從緩存池中獲取
op = free_list[numfree];
//設(shè)置引用計(jì)數(shù)
_Py_NewReference((PyObject *)op);
#ifdef SHOW_ALLOC_COUNT
count_reuse++;
#endif
} else {
//不可用的時(shí)候,申請(qǐng)內(nèi)存
op = PyObject_GC_New(PyListObject, &PyList_Type);
if (op == NULL)
return NULL;
#ifdef SHOW_ALLOC_COUNT
count_alloc++;
#endif
}
//如果size等于0,ob_item設(shè)置為NULL
if (size <= 0)
op->ob_item = NULL;
else {
//否則的話,創(chuàng)建一個(gè)指定容量的指針數(shù)組,然后讓ob_item指向它
//所以是先創(chuàng)建PyListObject對(duì)象, 然后創(chuàng)建指針數(shù)組
//最后通過(guò)ob_item建立聯(lián)系
op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
}
//設(shè)置ob_size和allocated,然后返回op
Py_SIZE(op) = size;
op->allocated = size;
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
我們注意到源碼里面有一個(gè)緩存池,是的,Python大部分對(duì)象都有自己的緩存池,只不過(guò)實(shí)現(xiàn)的方式不同。
列表的銷毀
創(chuàng)建PyListObject對(duì)象時(shí),會(huì)先檢測(cè)緩存池free_list里面是否有可用的對(duì)象,有的話直接拿來(lái)用,否則通過(guò)malloc在系統(tǒng)堆上申請(qǐng)。列表的緩存池是使用數(shù)組實(shí)現(xiàn)的,里面最多維護(hù)80個(gè)PyListObject對(duì)象。
#ifndef PyList_MAXFREELIST
#define PyList_MAXFREELIST 80
#endif
static PyListObject *free_list[PyList_MAXFREELIST];
根據(jù)之前的經(jīng)驗(yàn)我們知道,既然創(chuàng)建的時(shí)候能從緩存池中獲取,那么在執(zhí)行析構(gòu)函數(shù)的時(shí)候也要把列表放到緩存池里面。
static void
list_dealloc(PyListObject *op)
{
Py_ssize_t i;
PyObject_GC_UnTrack(op);
Py_TRASHCAN_SAFE_BEGIN(op)
//先釋放底層數(shù)組
if (op->ob_item != NULL) {
i = Py_SIZE(op);
//但是釋放之前,還有一件重要的事情
//要將底層數(shù)組中每個(gè)指針指向的對(duì)象的引用計(jì)數(shù)都減去1
//因?yàn)樗鼈儾辉俪钟袑?duì)"對(duì)象"的引用
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]);
}
//然后釋放底層數(shù)組所占的內(nèi)存
PyMem_FREE(op->ob_item);
}
//判斷緩沖池里面PyListObject對(duì)象的個(gè)數(shù),如果沒(méi)滿,就添加到緩存池
//注意:我們看到執(zhí)行到這一步的時(shí)候, 底層數(shù)組已經(jīng)被釋放掉了
if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
//添加到緩存池的時(shí)候,是添加到尾部
//獲取的時(shí)候也是從尾部獲取
free_list[numfree++] = op;
else
//否則的話就釋放掉PyListObject對(duì)象所占的內(nèi)存
Py_TYPE(op)->tp_free((PyObject *)op);
Py_TRASHCAN_SAFE_END(op)
}
我們知道在創(chuàng)建一個(gè)新的PyListObject對(duì)象時(shí),實(shí)際上是分為兩步的,先創(chuàng)建PyListObject對(duì)象,然后創(chuàng)建底層數(shù)組,最后讓PyListObject對(duì)象中的ob_item成員指向這個(gè)底層數(shù)組。
同理,在銷毀一個(gè)PyListObject對(duì)象時(shí),先銷毀ob_item維護(hù)的底層數(shù)組,然后再釋放PyListObject對(duì)象自身(如果緩存池已滿)。
現(xiàn)在可以很清晰地明白了,原本空蕩蕩的緩存池其實(shí)是被已經(jīng)死去的PyListObject對(duì)象填充了。在以后創(chuàng)建新的PyListObject對(duì)象時(shí),Python會(huì)首先喚醒這些死去的PyListObject對(duì)象,給它們一個(gè)洗心革面、重新做人的機(jī)會(huì)。但需要注意的是,這里緩存的僅僅是PyListObject對(duì)象,對(duì)于底層數(shù)組,其ob_item已經(jīng)不再指向了。
從list_dealloc中我們看到,PyListObject對(duì)象在放進(jìn)緩存池之前,ob_item指向的數(shù)組就已經(jīng)被釋放掉了,同時(shí)數(shù)組中指針指向的對(duì)象的引用計(jì)數(shù)會(huì)減1。所以最終數(shù)組中這些指針指向的對(duì)象也大難臨頭各自飛了,或生存、或毀滅,總之此時(shí)和PyListObject之間已經(jīng)沒(méi)有任何聯(lián)系了。
但是為什么要這么做呢?為什么不連底層數(shù)組也一起維護(hù)呢?可以想一下,如果繼續(xù)維護(hù)的話,數(shù)組中指針指向的對(duì)象永遠(yuǎn)不會(huì)被釋放,那么很可能會(huì)產(chǎn)生懸空指針的問(wèn)題,所以這些指針指向的對(duì)象所占的空間必須交還給系統(tǒng)(前提是沒(méi)有其它指針指向了)。
但是實(shí)際上,是可以將PyListObject對(duì)象維護(hù)的底層數(shù)組進(jìn)行保留的,即:只將數(shù)組中指針指向的對(duì)象的引用計(jì)數(shù)減1,然后將數(shù)組中的指針都設(shè)置為NULL,不再指向之前的對(duì)象了,但是并不釋放底層數(shù)組本身所占用的內(nèi)存空間。
因此這樣一來(lái),釋放的內(nèi)存不會(huì)交給系統(tǒng)堆,那么再次分配的時(shí)候,速度會(huì)快很多。但是這樣帶來(lái)一個(gè)問(wèn)題,就是這些內(nèi)存沒(méi)人用也會(huì)一直占著,并且只能供PyListObject對(duì)象的ob_item指向的底層數(shù)組使用。因此Python還是為避免消耗過(guò)多內(nèi)存,采取將底層數(shù)組所占的內(nèi)存交還給了系統(tǒng)堆這樣的做法,在時(shí)間和空間上選擇了空間。
lst1 = [1, 2, 3]
print(id(lst1)) # 1243303086208
# 扔到緩存池中,放在數(shù)組的尾部
del lst1
# 從緩存池中獲取,也會(huì)從數(shù)組的尾部開(kāi)始拿
lst2 = [1, 2, 3]
print(id(lst2)) # 1243303086208
# 因此打印的地址是一樣的
小結(jié)
作為一個(gè)功能強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),多花些時(shí)間是有必要的。
原文鏈接:https://juejin.cn/post/7089263511049601031
相關(guān)推薦
- 2023-02-10 Docker不同網(wǎng)段下的容器互聯(lián)的實(shí)現(xiàn)_docker
- 2022-08-10 對(duì)WPF中的TreeView實(shí)現(xiàn)右鍵選定_C#教程
- 2022-04-19 C語(yǔ)言雙指針?biāo)惴ㄅ笥堰^(guò)情人節(jié)我過(guò)算法_C 語(yǔ)言
- 2022-12-23 swift內(nèi)存管理指針類型使用實(shí)例詳解_Swift
- 2022-07-29 C++數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)現(xiàn)_C 語(yǔ)言
- 2022-08-15 oracle數(shù)據(jù)庫(kù)表實(shí)現(xiàn)自增主鍵的方法實(shí)例_oracle
- 2022-07-08 C#中的Dialog對(duì)話框_C#教程
- 2023-07-15 uni-app 組件與頁(yè)面事件傳值 this.$emit
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支