原始碼位置 Include/dictobject.h | Objects/dictobject.c
PyDictObject的儲存策略
1 2 3 4 5 6 7 8 9 |
1. 使用雜湊表進行儲存 2. 使用開放定址法處理衝突 2.1 插入, 發生衝突, 通過二次探測演算法, 尋找下一個位置, 直到找到可用位置, 放入(形成一條衝突探測鏈) 2.2 查詢, 需要遍歷衝突探測鏈 2.3 刪除, 如果物件在探測鏈上, 不能直接刪除, 否則會破壞整個結構(所以不是真的刪) |
關於 hash表的 wiki
基本鍵值PyDictEntry
1 2 3 4 5 |
typedef struct { Py_ssize_t me_hash; PyObject *me_key; PyObject *me_value; } PyDictEntry; |
說明
1 2 3 4 |
1. PyDictEntry 用於儲存鍵值對資訊 2. Py_ssize_t me_hash 儲存了me_key計算得到的hash值, 不重複計算 |
結構
PyDictEntry的三個狀態(圖片引自-Python原始碼剖析)
PyDictObject定義
定義
1 2 3 4 5 6 7 8 9 10 11 12 |
typedef struct _dictobject PyDictObject; struct _dictobject { PyObject_HEAD Py_ssize_t ma_fill; Py_ssize_t ma_used; Py_ssize_t ma_mask; PyDictEntry *ma_table; PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash); PyDictEntry ma_smalltable[PyDict_MINSIZE]; }; |
說明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
1. PyObject_HEAD 反而宣告為定長物件, 因為ob_size在這裡用不上, 使用ma_fill和ma_used計數 2. Py_ssize_t ma_fill; Py_ssize_t ma_used; ma_fill = # Active + # Dummy ma_used = # Active 3. Py_ssize_t ma_mask; 雜湊表entry容量 = ma_mask + 1, 初始值ma_mask = PyDict_MINSIZE - 1 = 7 ma_mask + 1 = # Unused + # Active + # Dummy 4. PyDictEntry *ma_table; 指向雜湊表記憶體, 如果是小的dict(entry數量 |
結構
結論
1 2 3 |
1. PyDictObject在生命週期內, 需要維護ma_fill/ma_used/ma_mask 三個計數值 2. 初始化建立是ma_smalltable, 超過大小後, 會使用外部分配的空間 |
構造過程
定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
PyObject * PyDict_New(void) { register PyDictObject *mp; // 初始化dummy if (dummy == NULL) { dummy = PyString_FromString(""); if (dummy == NULL) return NULL; // 暫時忽略 #ifdef SHOW_CONVERSION_COUNTS Py_AtExit(show_counts); #endif #ifdef SHOW_ALLOC_COUNT Py_AtExit(show_alloc); #endif #ifdef SHOW_TRACK_COUNT Py_AtExit(show_track); #endif } // 如果PyDictObject緩衝池可用 if (numfree) { // 取緩衝池最後一個可用物件 mp = free_list[--numfree]; assert (mp != NULL); assert (Py_TYPE(mp) == &PyDict_Type); _Py_NewReference((PyObject *)mp); // 初始化 if (mp->ma_fill) { // 1. 清空 ma_smalltable // 2. ma_used = ma_fill = 0 // 3. ma_table -> ma_smalltable // 4. ma_mask = PyDict_MINSIZE - 1 = 7 EMPTY_TO_MINSIZE(mp); } else { // 1. ma_table -> ma_smalltable // 2. ma_mask = PyDict_MINSIZE - 1 = 7 INIT_NONZERO_DICT_SLOTS(mp); } assert (mp->ma_used == 0); assert (mp->ma_table == mp->ma_smalltable); assert (mp->ma_mask == PyDict_MINSIZE - 1); #ifdef SHOW_ALLOC_COUNT count_reuse++; #endif } else { // 建立一個 mp = PyObject_GC_New(PyDictObject, &PyDict_Type); if (mp == NULL) return NULL; // 初始化 1/2/3/4 EMPTY_TO_MINSIZE(mp); #ifdef SHOW_ALLOC_COUNT count_alloc++; #endif } // 搜尋方法, 關注這個 mp->ma_lookup = lookdict_string; #ifdef SHOW_TRACK_COUNT count_untracked++; #endif #ifdef SHOW_CONVERSION_COUNTS ++created; #endif // 返回 return (PyObject *)mp; } |
簡化步驟
1 2 3 4 5 6 7 |
1. 如果PyDictObject物件緩衝池有, 從裡面直接取, 初始化 2. 否則, 建立一個, 初始化 3. 關聯搜尋方法lookdict_string 4. 返回 |
結論
1 2 3 |
1. 關注物件緩衝池 2. 關注lookdict_string |
銷燬過程
方法定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
static void dict_dealloc(register PyDictObject *mp) { register PyDictEntry *ep; Py_ssize_t fill = mp->ma_fill; PyObject_GC_UnTrack(mp); Py_TRASHCAN_SAFE_BEGIN(mp) // 遍歷銷燬ma_table中元素 (ma_table可能指向small_table 或 外部) for (ep = mp->ma_table; fill > 0; ep++) { if (ep->me_key) { --fill; Py_DECREF(ep->me_key); Py_XDECREF(ep->me_value); } } // 如果指向外部, 銷燬整個(上面一步只銷毀內部元素) if (mp->ma_table != mp->ma_smalltable) PyMem_DEL(mp->ma_table); // 如果物件緩衝池未滿且是PyDict_Type, 放入 if (numfree tp_free((PyObject *)mp); Py_TRASHCAN_SAFE_END(mp) } |
PyDictObject物件緩衝池
定義
1 2 3 4 5 6 |
#ifndef PyDict_MAXFREELIST #define PyDict_MAXFREELIST 80 #endif static PyDictObject *free_list[PyDict_MAXFREELIST]; static int numfree = 0; |
物件緩衝池的結構(跟PyListObject物件緩衝池結構基本一樣)
結論
1 2 3 4 5 |
1. 最多會快取80個物件 2. 只快取 PyDictObject 本身, 其PyDictEntry全部會被回收 3. 快取物件進去, 舊有的值沒有變化, 取出來用的時候初始化時才改變 |
Dict 操作
查詢/插入/resize/刪除, 下個版本補
changelog
1 |
2014-08-11 first version |
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式