热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python集合对象底层实现源码分析PySetObject(set)

PySetObject本文参考的是3.8.0a0版本的代码,详见cpython源码分析基本篇以后都在github更新,请参考图解pythonsets

PySetObject

本文参考的是 3.8.0a0 版本的代码,详见  cpython 源码分析 基本篇

以后都在 github 更新,请参考 图解 python set

 

set 的实现方式和 dict 有点类似,但是稍微简单一些,我们来看看 set 的 memory layout

我们来看下 set_lookkey 这个函数

/* Objects/setobject.c57 - 133 行
*/static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)
{/* 给定一个 key, 和一个 hash 值,返回这个 hash 在这个集合 so 里对应的 entry */setentry *table;setentry *entry;size_t perturb;size_t mask = so->mask;size_t i = (size_t)hash & mask; /* 把 hash 高于 mask 长度的位清零,留下长度低于 mask 位数 */size_t j;int cmp;entry = &so->table[i]; /* 取出集合的第 i 个 entry */if (entry->key == NULL) /* 如果第 i 个 entry 是空的值,直接返回 */return entry;perturb = hash;while (1) {/* 第i个 entry 不为空, 开始循环匹配,直到找到相等的 entry 为止 */if (entry->hash == hash) {/* 如果 entry 里的 hash 值相同,判断 key 是否相同 */PyObject *startkey = entry->key;/* startkey cannot be a dummy because the dummy hash field is -1 */assert(startkey != dummy);if (startkey == key) /* key 地址相同,返回entry */return entry;if (PyUnicode_CheckExact(startkey)&& PyUnicode_CheckExact(key)&& _PyUnicode_EQ(startkey, key)) /* key 为string,且 string 值相同,返回 entry */return entry;table = so->table;Py_INCREF(startkey);cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); /* 判断 entry 里存储的 key 和传入的 key 的结果 */Py_DECREF(startkey);if (cmp <0) /* start key 和 传入的 key 不相等 */return NULL;if (table != so->table || entry->key != startkey) /* unlikely */return set_lookkey(so, key, hash);if (cmp > 0) /* start key 和 传入的 key 相等 */return entry;mask = so->mask; /* 避免寄存器溢出? */}/* 当前的 entry 的 hash 值和 传入的 hash 值不匹配,需要重新寻找一个位置 关于 LINEAR_PROBES 的作用可以看下面的介绍*/if (i + LINEAR_PROBES <= mask) {/* 判断 i 后面是否还有 LINEAR_PROBES 个空间,如果有则进行横向搜索 */for (j = 0 ; j hash == 0 && entry->key == NULL)return entry;if (entry->hash == hash) {PyObject *startkey = entry->key;assert(startkey != dummy);if (startkey == key)return entry;if (PyUnicode_CheckExact(startkey)&& PyUnicode_CheckExact(key)&& _PyUnicode_EQ(startkey, key))return entry;table = so->table;Py_INCREF(startkey);cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);Py_DECREF(startkey);if (cmp <0)return NULL;if (table != so->table || entry->key != startkey)return set_lookkey(so, key, hash);if (cmp > 0)return entry;mask = so->mask;}}}/* 横向的 LINEAR_PROBES 无法搜索到对应的 entry, 重新进行 hash, 返回到 while 开始处 */perturb >>= PERTURB_SHIFT;i = (i * 5 + 1 + perturb) & mask;entry = &so->table[i];if (entry->key == NULL)return entry;}
}

LINEAR_PROBES

当当前hash对应的 entry 的值不匹配时,传统的思路,直接重新生成一个 hash 值,在对应的新的 hash 值上找到一个新的位置,但是这样做的话对 cpu 的 cache 影响较大,如果两个位置间隔过于分散/随机,cpu 这一次读取了这个 entry 和附近的entry 到 cache 中,下一次又需要重新读取,浪费 cpu cycle

当前的算法引入一个 LINEAR_PROBES,在当前 entry 向前 LINEAR_PROBES 个位置进行寻找,如果找不到才重新进行 hash 计算,以提高 cpu cache 的稳定性,所以在 set 的 hash entry 是随机值和连续值的结合体

static int set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash): Objects/setobject.c 137-258行,经历和上边代码注释的 set_lookkey 函数相似的搜索过程,找对已经有值/空的 entry, 并把 entry 设置为传入的 key 的过程

static void set_insert_clean(setentry *table, size_t mask, PyObject *key, Py_hash_t hash): Objects/setobject.c 268-293行,找到一个空的 entry, 并把 key 插入 

 

下面我们来看下  set_table_resize

/* Objects/setobject.c303 - 381 行
*/
static int
set_table_resize(PySetObject *so, Py_ssize_t minused)
{setentry *oldtable, *newtable, *entry;Py_ssize_t oldmask = so->mask;size_t newmask;int is_oldtable_malloced;setentry small_copy[PySet_MINSIZE];assert(minused >= 0);/* 找到第一个大于 minused 的值 */size_t newsize = PySet_MINSIZE;while (newsize <= (size_t)minused) {newsize <<= 1; // The largest possible value is PY_SSIZE_T_MAX + 1.}/* 给新的表创建空间 */oldtable = so->table;assert(oldtable != NULL);is_oldtable_malloced = oldtable != so->smalltable;if (newsize == PySet_MINSIZE) {/* 我们在进行缩小 set 的操作 */newtable = so->smalltable;if (newtable == oldtable) {if (so->fill == so->used) {/* fill 和 used 相等,没有被标记删除的数据,不需要做任何操作了 */return 0;}/* 把 so->table 临时拷贝到 small_copy 中,并把 oldtable 指向 small_copy */assert(so->fill > so->used);memcpy(small_copy, oldtable, sizeof(small_copy));oldtable = small_copy;}}else {newtable = PyMem_NEW(setentry, newsize);if (newtable == NULL) {PyErr_NoMemory();return -1;}}/* 清空 newtable */assert(newtable != oldtable);memset(newtable, 0, sizeof(setentry) * newsize);so->mask = newsize - 1;so->table = newtable;/* 利用上面的 set_insert_clean 把非标记成删除数据的 key 全部复制到新的 table 上 */newmask = (size_t)so->mask;if (so->fill == so->used) {for (entry = oldtable; entry <= oldtable + oldmask; entry++) {if (entry->key != NULL) {set_insert_clean(newtable, newmask, entry->key, entry->hash);}}} else {so->fill = so->used;for (entry = oldtable; entry <= oldtable + oldmask; entry++) {if (entry->key != NULL && entry->key != dummy) {set_insert_clean(newtable, newmask, entry->key, entry->hash);}}}if (is_oldtable_malloced)PyMem_DEL(oldtable);return 0;
}

static int set_contains_entry(PySetObject *so, PyObject *key, Py_hash_t hash): Objects/setobject.c 382-391行,利用 set_lookkey 方法检查这个 key 是否在该 set 中 

static int set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash): Objects/setobject.c 396-413行,把对应的 entry 标记为删除

static int set_add_key(PySetObject *so, PyObject *key): Objects/setobject.c 415-427行,调用 set_add_entry 把对应的 key 插入 set 的 table 中

之后的函数也很好理解,就不逐一说


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
author-avatar
手机用户2602932547
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有