09月17, 2020

45. Set 是如何工作的(1)

Set 的功能其实与 Dict 的 Key 非常相似:

  • 不可重复;
  • 需要快速查找;

一些文章中认为 Set 是 Value 为空的 Dict,在逻辑上大体没问题,但具体实现还是有很大区别的。

PySetObject 的结构

对象的结构在很大程度上决定了它能做什么,首先了解 Set 的结构:

// Objects/setobject:42

typedef struct {
    PyObject_HEAD

    Py_ssize_t fill;            /* 所有已使用节点,包括 dummy 节点*/
    Py_ssize_t used;            /* 有效节点数量 */

    Py_ssize_t mask;        /* 用于对 Key 的 hash 按哈希表大小取模 */

    setentry *table;        /* 哈希表 */
    Py_hash_t hash;             /* frozenset 的 hash */
    Py_ssize_t finger;          /* Search finger for pop() */

    setentry smalltable[PySet_MINSIZE];    /* 初始的小哈希表 */
    PyObject *weakreflist;      /* 弱引用表 */
} PySetObject;

在定义中隐约可以看到一些 Dict 的影子,比如 mask、fill、used、table 等等。下面将 Set 与 Dict 放在一起比较一下。

image.png

smalltable 是所有 PySetObject 创建后,table 指向的位置,这样设计的目的是为了减少一次内存申请调用,另外确保 table 指针始终不为 NULL,免去重复 if(Set->table == NULL) 这样的测试代码。我已经将 Dict 和 Set 功能相似区域标记出来了,其中 table 指针的两种可能用虚线标出,两种不同情况下的 table 内存用深浅不一的绿色区别。

如果再观察 Set 中 entry 的结构,就更明显了。

image.png

同样的,我将相似功能部分标记出来了。

Set 几乎就是一个减配版 Dict,取消了 Value 字段。底层的主要区别在于:

  • entries 的存储不同,Set 的数据直接存储在哈希表里,Dict 的哈希表与数据是分离的;
  • Set 与 Dict 的 lookup 函数有细微区别。

不同于 Dict 直接采用二次探测,Set 会先进行一小段线性搜索,再进行二次探测。可以猜到 Set 的 lookup 与 Dict 必然存在相当的相似度。

需要指明的是, Dict 与 Set 的相似性更多的是逻辑上,实现差异依然很大。

本文链接:http://www.thinkinpython.com/post/deep_python_vm_45.html

-- EOF --