py

作者: sustainer123 (caster)   2024-11-12 16:41:44
姆咪 這個好麻煩 不過比ml文章好寫
作為一個高階語言,python擁有自動GC的機制
在本文中,我們將討論python最常用的編譯器:Cpython的GC
引用記數機制是Cpython GC的重要環節
首先 我們將聚焦於引用計數機制
引用計數機制
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
Cpython定義的物件長成上面那樣子
ob_refcnt就是引用計數
引用計數的概念並不複雜
當我們創建一個物件,該物件的引用計數就加一
反之,刪除一個物件,引用計數減一
import sys
a = 1
print(sys.getrefcount(1))
del a
print(sys.getrefcount(1))
這時候output會是一串很長的數字(1000000052)
直觀上,我們會預期out會是1跟0
最多基於某些隱式引用,output可能會多一點
但不該是一串很長的數字
之所以會跑出一串很長的數字
這是因為某些小整數經常被使用,為了避免不斷為這些小整數分配與釋出記憶體
程式開始執行時,Cpython預先為這些整數分配記憶體並創建物件
這些小整數已經被多次引用,所以引用計數才會特別高
小整數的範圍是-5到256(這是Cpython設定的範圍,隨著編譯器不同而改變。)
import sys
a = 999
print(sys.getrefcount(999))
del a
print(sys.getrefcount(999))
改成999 ,這時候output就4與3,之所以不是1與0,這是因為隱式引用
諸如sys.getrefcount()本身就有一次臨時引用
Cpyhton為了最佳化,編譯時會把變數存入co_consts,這時引用數又加一。
假如引用記數歸零,Cpython會銷毀該物件,並釋放記憶體。
...
#ifndef PyFloat_MAXFREELIST
# define PyFloat_MAXFREELIST 100
#endif
...
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
...
};
...
static void
float_dealloc(PyFloatObject *op)
{
#if PyFloat_MAXFREELIST > 0
if (PyFloat_CheckExact(op)) {
struct _Py_float_state *state = get_float_state();
#ifdef Py_DEBUG
// float_dealloc() must not be called after _PyFloat_Fini()
assert(state->numfree != -1);
#endif
if (state->numfree >= PyFloat_MAXFREELIST) {
PyObject_Free(op);
return;
}
state->numfree++;
Py_SET_TYPE(op, (PyTypeObject *)state->free_list);
state->free_list = op;
}
else
#endif
{
Py_TYPE(op)->tp_free((PyObject *)op);
}
}
前面主要聚焦於整數的引用計數與回收,現在讓我們研究浮點數的回收機制
當浮點數的引用計數歸零,這時會有兩種可能
假若浮點數的freelist <100,浮點數放入freelist
假設freelist >= 100,則銷毀浮點數並釋放空間
Cpython設置free list的用意也是為了避免頻繁地記憶體分配與釋放
引用計數的問題
import gc
a = []
a.append(a)
del a
print(gc.collect())
這邊發生循環引用,首先創建a,引用計數+=1
在 a.append(a) 之後,a 這個列表對象內部有一個元素是自己,這樣 a 的引用計數變
成 2。
後面del a,引用計數-=1,我們預期a會被引用記數回收,但此時a的引用計數是1,因為a
包含對自己的引用,所以a不會被回收。
gc.collect()是手動觸發其他回收機制,因為引用計數無法清理a,其他機制能解決循環
引用的問題,所以a被其他機制清除。gc.collect()回傳1,代表一個物件被清除。
所以,單單依靠引用計數會出現上述的麻煩,這時候就有了其他配套機制,
參考文章:
https://hackmd.io/@8thEdition/cpython_garbage_collection_reference_counting

Links booklink

Contact Us: admin [ a t ] ucptt.com