日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

解密列表的創建與銷毀,以及緩存池長什么樣子?

來源: 責編: 時間:2024-07-24 14:49:38 608觀看
導讀楔子前面我們分析了列表的底層結構和擴容機制,本篇文章來聊一聊列表的創建和銷毀,以及緩存池。列表的創建創建列表,解釋器只提供了唯一的一個 Python/C API,也就是 PyList_New。這個函數接收一個 size 參數,允許我們在創建

楔子

Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

前面我們分析了列表的底層結構和擴容機制,本篇文章來聊一聊列表的創建和銷毀,以及緩存池。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

列表的創建

Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

創建列表,解釋器只提供了唯一的一個 Python/C API,也就是 PyList_New。這個函數接收一個 size 參數,允許我們在創建 PyListObject 對象時指定底層的 PyObject * 數組的長度。Rb028資訊網——每日最新資訊28at.com

//Objects/listobject.cPyObject *PyList_New(Py_ssize_t size){       // 聲明一個 PyListObject * 變量    // 指向即將創建的 PyListObject 對象    PyListObject *op;    // 底層數組的長度必須大于等于 0    if (size < 0) {        PyErr_BadInternalCall();        return NULL;    }        // PyList_MAXFREELIST 是一個宏,表示緩存池的容量    // 編譯時可以選擇是否禁用緩存池,默認不禁用,容量為 80#if PyList_MAXFREELIST > 0    // 獲取進程狀態對象內部的緩存池    struct _Py_list_state *state = get_list_state();    // state->numfree 表示緩存池中已緩存的元素個數    // 如果大于 0,證明有可用元素,那么會從緩存池中獲取    if (PyList_MAXFREELIST && state->numfree) {        // 可用元素的數量減一        state->numfree--;        // 獲取緩存的列表指針,并將指向的列表的引用計數設置為 1        op = state->free_list[state->numfree];        OBJECT_STAT_INC(from_freelist);        _Py_NewReference((PyObject *)op);    }    else#endif    {        // 如果緩存池被禁用,或者緩存池中沒有可用元素        // 那么通過 PyObject_GC_New 申請內存        // 問題來了,之前申請內存不是用的 PyObject_New 嗎        // 這里為啥換成 PyObject_GC_New 呢?我們稍后再說        op = PyObject_GC_New(PyListObject, &PyList_Type);        if (op == NULL) {            return NULL;        }    }    // 如果 size <= 0,那么一定等于 0,此時列表不包含任何元素    if (size <= 0) {        // 那么 ob_item 直接設置為 NULL        op->ob_item = NULL;    }    else {        // 否則為底層數組申請內存,因為存儲的都是指針        // 所以大小為 size * sizeof(PyObject *)        op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));        if (op->ob_item == NULL) {            Py_DECREF(op);            return PyErr_NoMemory();        }    }    // 將 ob_size 和 allocated 均設置為 size    Py_SET_SIZE(op, size);    op->allocated = size;    // 讓列表被 GC 跟蹤    _PyObject_GC_TRACK(op);    // 轉成泛型指針之后返回    return (PyObject *) op;}

整個過程非常好理解,就是先創建一個 PyListObject 對象,然后再為底層數組申請內存,最后通過 ob_item 字段將兩者關聯起來。當然這個過程中會使用緩存池,關于緩存池一會兒再聊。Rb028資訊網——每日最新資訊28at.com

然后還要說一下內存申請函數,在這之前我們看到申請內存用的都是 PyObject_New 函數,它和這里的 PyObject_GC_New 有什么區別呢?由于涉及到 Python 的內存管理,我們暫時先不聊那么深,大家先有個基本了解即可,等到介紹內存管理和垃圾回收的時候會詳細剖析。Rb028資訊網——每日最新資訊28at.com

我們知道 Python 對象在底層都是一個結構體,并且結構體內部嵌套了 PyObject。但對于那些能夠產生循環引用的可變對象來說,它們除了 PyObject 之外,還包含了一個 PyGC_Head,用于垃圾回收。Rb028資訊網——每日最新資訊28at.com

所以 PyObject_New 和 PyObject_GC_New 接收的參數是一樣的,但后者會多申請 16 字節的內存,這 16 字節是為 PyGC_Head 準備的。那么問題來了,PyGC_Head 在什么地方呢?Rb028資訊網——每日最新資訊28at.com

圖片圖片Rb028資訊網——每日最新資訊28at.com

PyGC_Head 就在 PyObject 的前面,但是注意:雖然為 PyGC_Head 申請了內存,但返回的是 PyObject 的地址。至于這里面的更多細節,后續在剖析內存管理和垃圾回收的時候細說,目前先簡單了解一下即可。Rb028資訊網——每日最新資訊28at.com

然后再說一下計算內存的兩種方式:Rb028資訊網——每日最新資訊28at.com

import syslst = []# 可以調用 __sizeof__ 方法計算對象的內存print(lst.__sizeof__())  # 40# 也可以通過 sys.getsizeof 函數print(sys.getsizeof(lst))  # 56

我們看到 sys.getsizeof 算出的結果會多出 16 字節,相信你能猜到原因,因為它將 PyGC_Head 也算進去了,而對象的 __sizeof__ 方法則不會算在內。Rb028資訊網——每日最新資訊28at.com

不過對于字符串、整數、浮點數這種不會產生循環引用的對象來說,由于沒有 PyGC_Head,所以兩種方式計算的結果是一樣的。Rb028資訊網——每日最新資訊28at.com

import sysprint("".__sizeof__())  # 49print(sys.getsizeof(""))  # 49print((123).__sizeof__())  # 28print(sys.getsizeof(123))  # 28

以上就是列表的創建,整個過程不難理解。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

列表的銷毀

Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

創建 PyListObject 對象時,會先檢測緩存池 free_list 里面是否有可用的對象,有的話直接拿來用,否則通過 malloc 在系統堆上申請。列表的緩存池是使用數組實現的,里面最多維護 80 個 PyListObject 對象。Rb028資訊網——每日最新資訊28at.com

// Include/internal/pycore_list.h#define PyList_MAXFREELIST 80struct _Py_list_state {#if PyList_MAXFREELIST > 0    // free_list 是一個 PyListObject * 數組,容量為 80    // 添加元素時會從數組的尾部添加    // 獲取元素時也會從數組的尾部獲取    PyListObject *free_list[PyList_MAXFREELIST];    // 緩存池中可用元素數量    int numfree;#endif};// Objects/listobject.cstatic struct _Py_list_state *get_list_state(void){       // 列表緩存池會在解釋器啟動時創建好,并放在進程狀態對象中    PyInterpreterState *interp = _PyInterpreterState_GET();    // 返回 struct _Py_list_state 實例    return &interp->list;}

根據之前的經驗我們知道,既然創建的時候能從緩存池中獲取,那么在執行析構函數的時候也要把列表放到緩存池里面。看一下列表的析構函數,它由 PyList_Type 的 tp_dealloc 字段負責,而該字段被設置為 list_dealloc。Rb028資訊網——每日最新資訊28at.com

// Objects/listobject.cstatic voidlist_dealloc(PyListObject *op){    Py_ssize_t i;    // 列表可能會產生循環引用,因此創建之后要被 GC 跟蹤    // 而現在要被回收了,所以也要取消 GC 跟蹤    PyObject_GC_UnTrack(op);    // 這一步的作用,稍后再說    Py_TRASHCAN_BEGIN(op, list_dealloc)    // 先釋放底層數組    if (op->ob_item != NULL) {        // 但是釋放之前,還有一件重要的事情        // 要將底層數組中每個指針指向的對象的引用計數都減去 1        // 因為它們不再持有對"對象"的引用        i = Py_SIZE(op);        while (--i >= 0) {            Py_XDECREF(op->ob_item[i]);        }        // 然后釋放底層數組所占的內存        PyMem_Free(op->ob_item);    }#if PyList_MAXFREELIST > 0    // 獲取緩存池    struct _Py_list_state *state = get_list_state();    // 如果已緩存的元素小于 80 個,并且 op 指向的是列表    // 那么將 op 追加到數組中,并將 numfree 自增 1    if (state->numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) {        state->free_list[state->numfree++] = op;        OBJECT_STAT_INC(to_freelist);    }    else#endif    {        // 否則將列表的內存釋放掉        Py_TYPE(op)->tp_free((PyObject *)op);    }    Py_TRASHCAN_END}

我們知道創建一個 PyListObject 對象會分為兩步,先創建 PyListObject 對象,然后創建底層數組,最后讓 PyListObject 對象的 ob_item 字段指向底層數組的首元素。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

同理,在銷毀一個 PyListObject 對象時,會先釋放 ob_item 維護的底層數組,然后在緩存池已滿的情況下再釋放 PyListObject 對象自身。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

現在我們算是明白了緩存池的機制,本來在銷毀列表時,要將它的內存釋放。但因為緩存池機制,解釋器并沒有這么做,而是將它的指針放在了緩存池里,至于列表對象則依舊駐留在堆上,只是我們已經無法再訪問了。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

當以后創建新的 PyListObject 對象時,解釋器會首先喚醒這些已經死去的 PyListObject 對象,給它們一個洗心革面、重新做人的機會。但需要注意的是,這里緩存的僅僅是 PyListObject 對象,對于底層數組,其 ob_item 已經不再指向了。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

從 list_dealloc 中我們可以看到,PyListObject 對象的指針在放進緩存池之前,ob_item 指向的數組就已經被釋放掉了,同時數組中指針指向的對象的引用計數會減 1。所以最終數組中這些指針指向的對象也大難臨頭各自飛了,或生存、或毀滅,總之此時和 PyListObject 之間已經沒有任何聯系了。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

但是為什么要這么做呢?為什么不連底層數組也一起維護呢?可以想一下,如果繼續維護的話,數組中指針指向的對象永遠不會被釋放,那么很可能會產生懸空指針的問題。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

但實際上是可以將底層數組進行保留的,做法是只將數組中指針指向的對象的引用計數減 1,然后將數組中的指針都設置為 NULL,不再指向之前的對象,但并不釋放底層數組本身所占用的內存空間。這樣一來釋放的內存不會交給系統堆,那么再次分配的時候,速度會快很多。但這樣會帶來兩個問題。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

1)這些內存沒人用也會一直占著,并且只能供 PyListObject 對象的 ob_item 指向的底層數組使用。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

2)基于緩存池獲取的列表的容量,和新創建的列表的容量不一定匹配。比如底層數組長度為 6 的 PyListObject * 被放入了緩存池,那么表示列表最多容納 6 個元素,但如果我們要創建一個長度為 8 的列表怎么辦?此時依舊要重新為底層數組申請內存。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

因此基于以上兩個原因,Python 選擇將底層數組所占的內存交還給了系統堆,當然也節省了內存。Rb028資訊網——每日最新資訊28at.com

lst1 = [1, 2, 3]print(id(lst1))  # 139671899367680# 扔到緩存池中,放在數組的尾部del lst1# 從緩存池中獲取,也會從數組的尾部開始拿lst2 = [1, 2, 3]print(id(lst2))  # 139671899367680# 因此打印的地址是一樣的

以上就是列表的創建和銷毀,以及它的緩存池原理。Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

trashcan 機制

Rb028資訊網——每日最新資訊28at.com

Rb028資訊網——每日最新資訊28at.com

在看列表的銷毀過程時,我們注意到里面有這么一行代碼。Rb028資訊網——每日最新資訊28at.com

圖片圖片Rb028資訊網——每日最新資訊28at.com

這是做什么的呢,首先在 Python 中,我們可以創建具有深度遞歸的對象,比如:Rb028資訊網——每日最新資訊28at.com

L = Nonefor i in range(2 ** 20):    L = [L]del L

此時的 L 就是一個嵌套了 2 ** 20 層的列表,當我們刪除 L 的時候,會先銷毀 L[0]、然后銷毀 L[0][0],以此類推,直到遞歸深度為 2 ** 20。Rb028資訊網——每日最新資訊28at.com

而這樣的深度毫無疑問會溢出 C 的調用棧,導致解釋器崩潰。但事實上我們在 del L 的時候解釋器并沒有崩潰,原因就是 CPython 發明了一種名為 trashcan 的機制,它通過延遲銷毀的方式來限制銷毀的遞歸深度。關于這一特性,我們知道就好了,不用太關注。Rb028資訊網——每日最新資訊28at.com

本文鏈接:http://m.www897cc.com/showinfo-26-103168-0.html解密列表的創建與銷毀,以及緩存池長什么樣子?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 璀璨星河有你·鴻蒙系列沙龍報名火熱進行中!

下一篇: 五種編寫&quot;自然&quot;代碼的方法,讓每個人都愛不釋手

標簽:
  • 熱門焦點
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
国产精品vip| 欧美视频日韩| 久久精品av麻豆的观看方式| 性色一区二区三区| 久久亚洲精品中文字幕冲田杏梨| 亚洲欧美亚洲| 久久久久欧美| 久久先锋资源| 欧美激情网友自拍| 欧美无砖砖区免费| 国产伦精品一区二区三区高清| 国产欧美一区二区三区在线老狼| 一区在线免费| 91久久精品美女高潮| 亚洲欧洲日夜超级视频| 亚洲一区二区三区在线看| 欧美自拍偷拍午夜视频| 久久视频在线视频| 欧美精品久久久久a| 国产精品国产三级国产aⅴ浪潮 | 亚洲国内自拍| 亚洲私人影院在线观看| 久久久www成人免费精品| 欧美人在线观看| 国产亚洲综合在线| 亚洲美女中出| 久久精品天堂| 欧美视频在线观看一区二区| 樱桃国产成人精品视频| 亚洲影视在线| 欧美激情网站在线观看| 国产午夜精品一区二区三区欧美| 亚洲激情偷拍| 久久国产精品久久久久久久久久 | 久久久噜噜噜久久人人看| 欧美另类视频| 黑人中文字幕一区二区三区| 亚洲无限av看| 欧美刺激性大交免费视频 | 一区二区三区四区蜜桃| 久久久久久尹人网香蕉| 国产精品成人播放| 亚洲国产欧美另类丝袜| 欧美在线视频免费播放| 欧美色精品天天在线观看视频| 在线播放中文字幕一区| 欧美一区二区三区在线| 欧美午夜剧场| 9l视频自拍蝌蚪9l视频成人| 美女网站在线免费欧美精品| 国产一区二区成人久久免费影院| 亚洲性感美女99在线| 欧美激情视频网站| 久久蜜桃精品| 久久九九99视频| 国产欧美高清| 亚洲免费一区二区| 欧美日韩精品一区二区三区| 91久久国产综合久久91精品网站| 久久久久九九九九| 国产午夜精品全部视频在线播放| 亚洲综合丁香| 国产精品青草综合久久久久99| 99在线精品观看| 欧美精品在线观看播放| 亚洲欧洲视频| 欧美国产精品久久| 亚洲国产精品久久久久秋霞蜜臀| 久久久久网址| 国内揄拍国内精品久久| 欧美制服丝袜| 国产亚洲精品一区二区| 午夜久久黄色| 国产女人水真多18毛片18精品视频| 亚洲午夜小视频| 国产精品久久久久久久免费软件| 一区二区三区波多野结衣在线观看| 欧美精品精品一区| 日韩视频在线观看一区二区| 欧美精品一区二区高清在线观看| 亚洲人成网站影音先锋播放| 蜜臀av国产精品久久久久| 亚洲成人直播| 欧美a级片网站| 亚洲欧洲精品一区二区三区| 欧美成人有码| 亚洲精品一区二区在线| 欧美精品午夜| 这里只有精品电影| 国产精品久久久久7777婷婷| 亚洲男同1069视频| 国产日韩精品视频一区| 久久久精品一品道一区| 在线看片一区| 欧美伦理影院| 亚洲影院色无极综合| 国产精品一区二区三区四区五区| 午夜精品久久久久久久久| 国产乱码精品一区二区三区五月婷| 先锋a资源在线看亚洲| 国产亚洲精品aa午夜观看| 久久久欧美精品| 亚洲激情在线观看| 欧美日韩综合| 欧美一区二区三区喷汁尤物| 黄色亚洲网站| 欧美激情精品久久久久久蜜臀| 一本久久a久久精品亚洲| 国产精品男女猛烈高潮激情| 久久不射网站| 亚洲欧洲另类| 国产精品日韩欧美| 久久爱www| 亚洲高清不卡av| 欧美日韩一区在线观看视频| 亚洲免费一在线| 黑人巨大精品欧美黑白配亚洲| 欧美成人精品在线视频| 亚洲性感激情| 韩国一区二区三区美女美女秀| 欧美国产三区| 亚洲自拍三区| 1769国产精品| 国产精品久久77777| 久久久欧美一区二区| 亚洲免费观看在线视频| 国产精品免费观看视频| 久久久久久久网| 999亚洲国产精| 国产亚洲精品久| 欧美精品三级日韩久久| 欧美一区二区三区播放老司机| 1769国产精品| 国产精品久久久久aaaa| 久久久999精品免费| 日韩一区二区精品葵司在线| 国产欧美日韩精品丝袜高跟鞋| 免费在线成人av| 亚洲影视综合| 在线免费观看日韩欧美| 国产精品久久中文| 美女成人午夜| 欧美一级大片在线观看| 亚洲精品美女免费| 国产综合香蕉五月婷在线| 欧美视频1区| 裸体丰满少妇做受久久99精品| 亚洲一区国产| 亚洲欧洲日产国产网站| 国产亚洲美州欧州综合国| 欧美日本不卡| 久久综合影音| 香蕉久久夜色精品国产| 日韩一区二区精品葵司在线| 国产一区二区日韩精品欧美精品| 欧美精品一区二区在线播放| 久久www成人_看片免费不卡| 99国产精品视频免费观看| 一区视频在线| 国产精品伦子伦免费视频| 欧美sm视频| 久久精品国产第一区二区三区最新章节| 一本一本久久a久久精品综合妖精 一本一本久久a久久精品综合麻豆 | 亚洲综合电影一区二区三区| 亚洲高清久久网| 国产精品视频一二三| 欧美激情一区二区三区成人| 久久久国产视频91| 亚洲欧美不卡| 99综合视频| 亚洲国产一区二区视频| 国内精品久久久久影院色| 国产精品伦一区| 欧美日韩一区自拍| 欧美精品成人一区二区在线观看| 久久人人爽人人爽| 欧美伊人影院| 午夜欧美大片免费观看| 亚洲视频每日更新| 亚洲精品日本| 亚洲国产天堂久久综合| 极品日韩久久| 国产主播一区| 国产视频一区二区在线观看 | 国产欧美日韩视频一区二区三区| 欧美视频在线免费看| 欧美精品18| 欧美大片免费久久精品三p | 亚洲久久在线| 亚洲精品综合精品自拍| 亚洲第一免费播放区| 好看的av在线不卡观看| 欧美sm视频| 国产亚洲成精品久久| 亚洲性视频h| 欧美日韩在线一区二区| 亚洲日韩欧美视频一区| 欧美大片免费| 亚洲高清在线观看| 久久一本综合频道| 国语自产在线不卡| 欧美一区二区三区在线视频 | 亚洲精品国产系列| 国产午夜精品在线观看|