《Python源碼剖析》閱讀筆記:第五章-dict對象

來源:互聯網
上載者:User
第五章-dict對象

 Python裡的dict和C++ STL的map一樣,都是映射容器(key->value),但實現原理不同。由於Python內部大量使用dict這種結構(比如字串對象的intese機制),效率要求很高,所以Python沒有使用STL map的平衡二叉樹,而採用雜湊表,最低能在O(1)時間內完成搜尋。
使用hash就必須解決衝突的問題,dict採用的是開放定址法。原因我覺得是開放定址法比拉鏈法能更好地利用CPU cache,cache命中率較高。
探測函數為 i = (i << 2) + i + perturb + 1; perturb每探測一次就除以2^5。

dict的雜湊表裡每個slot都是一個自訂的entry結構:
typedef struct {

Py_ssize_t me_hash;
PyObject *me_key;
PyObject *me_value;

} PyDictEntry;
意義顧名思義,不多說了。

每個entry有三種狀態:Active, Unused, Dummy。
Unused:me_key == me_value == NULL,即未使用的空閑狀態。
Active:me_key != NULL, me_value != NULL,即該entry已被佔用
Dummy:me_key == dummy, me_value == NULL。
雜湊探測結束的條件是探測到一個Unused的entry。但是dict操作中必定會有刪除操作,如果刪除時僅把Active標記成Unused,顯然該entry之後的所有entry都不可能被探測到,所以引入了dummy結構。遇到dummy就說明當前entry處於空閑狀態,但探測不能結束。這樣就解決了刪除一個entry之後探測鏈斷裂的問題。

dict對象的定義為:
struct _dictobject {

PyObject_HEAD
Py_ssize_t ma_fill; /* # Active + # Dummy */
Py_ssize_t ma_used; /* # Active */

Py_ssize_t ma_mask;

PyDictEntry *ma_table;
PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);
PyDictEntry ma_smalltable[PyDict_MINSIZE];

};
ma_fill記錄Active + Dummy狀態的entry數。
ma_used記錄Active狀態的entry數。
ma_mask等於slot總數 - 1。因為一個key的雜湊值很可能超過slot總數,所以作為索引時得把它約束在slot總數的範圍內。而slot總數在定義的時候必須是2的乘冪,比如0x1000,所以減1之後就成了mask:0x111。再和hash做個&操作就能把索引之限制在0~0x111之間,即slot總數0x1000個,比較巧妙:)
ma_smalltable是預設的slot,初始有PyDict_MINSIZE個。
ma_table初始指向ma_smalltable,如果後期擴容,則指向新的slot空間。
ma_lookup為搜尋函數指標

dict對象的建立很簡單,先看看緩衝的對象池裡有沒有可用對象,如果有就直接用,沒有就從堆上申請。把fill和used域設成0。由於Python中把字串作為key的情況很多,所有搜尋函數就有一個針對string最佳化過的版本:lookdict_string。如果在檢查時發現key不是string對象,則調用預設的lookdict函數搜尋。

dict的插入操作由insertdict函數完成。插入操作的意義是:如果不存在key-value則插入,存在則覆蓋。所以先通過ma_lookup所指向的函數得到key所對應的entry。如果value不等於NULL,說明找到,將key指標替換。否則就直接在返回的entry上設定新的key-value對。
Python在處理d[key] = value這樣的運算式的時候調用的是insertdict函數的封裝函數PyDict_SetItem。PyDict_SetItem會計算key的雜湊值,然後把需要的資訊傳遞給insertdict。然後根據ma_table剩餘空間的大小決定是否resize。傳說和理論證明超過容量的2/3時衝突的機率大大增加,所以超過2/3後會進行擴容。

dict裡entry的刪除更簡單,算出雜湊值,找到entry,將其從Active轉換成Dummy,並調整table的容量。

最後是對象池。和前面list對象池一樣,dealloc時只回收table的記憶體,然後將dict放到池中,供後來new時再用。減少向堆申請記憶體的操作。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.