热门标签 | 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 中

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


推荐阅读
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 在Python编程中,当遇到程序运行无响应的问题时,通常与计算资源的消耗有关。Python使用任意精度整数进行计算,这意味着在处理大数值运算时,如计算大指数值,系统可能会因为内存或CPU资源不足而变得缓慢,甚至没有反馈。此外,代码中的无限循环或递归调用也可能导致类似问题。建议检查代码逻辑,优化算法效率,并确保计算任务不会超出系统的处理能力。 ... [详细]
  • 2022年Python面试题一.Python基础二.企业面试题结束语🥇🥇🥇✅作者简介:大家好我是编程IDὌ ... [详细]
  • PyTorch 2.0来了!100%向后兼容,一行代码将训练提速76%!
    点击下方卡片,关注“CVer”公众号AICV重磅干货,第一时间送达点击进入—CV微信技术交流群转载自:机器之心PyTorch官方 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • com.hazelcast.config.MapConfig.isStatisticsEnabled()方法的使用及代码示例 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • 在当前的软件开发领域,Lua 作为一种轻量级脚本语言,在 .NET 生态系统中的应用逐渐受到关注。本文探讨了 Lua 在 .NET 环境下的集成方法及其面临的挑战,包括性能优化、互操作性和生态支持等方面。尽管存在一定的技术障碍,但通过不断的学习和实践,开发者能够克服这些困难,拓展 Lua 在 .NET 中的应用场景。 ... [详细]
  • AIX编程挑战赛:AIX正方形问题的算法解析与Java代码实现
    在昨晚的阅读中,我注意到了CSDN博主西部阿呆-小草屋发表的一篇文章《AIX程序设计大赛——AIX正方形问题》。该文详细阐述了AIX正方形问题的背景,并提供了一种基于Java语言的解决方案。本文将深入解析这一算法的核心思想,并展示具体的Java代码实现,旨在为参赛者和编程爱好者提供有价值的参考。 ... [详细]
  • 本文深入探讨了 Git 与 SVN 的高效使用技巧,旨在帮助开发者轻松应对版本控制中的各种挑战。通过详细解析两种工具的核心功能与最佳实践,读者将能够更好地掌握版本管理的精髓,提高开发效率。 ... [详细]
  • 这是一个愚蠢的问题,但我只是对此感到好奇.假设我在Pythonshell,我有一些我查询的数据库对象.我做:db.query(的queryString)该查询在0xffdf842c ... [详细]
  • 开发笔记:Python之路第一篇:初识Python
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Python之路第一篇:初识Python相关的知识,希望对你有一定的参考价值。Python简介& ... [详细]
  • 详解 Python 的二元算术运算,为什么说减法只是语法糖?[Python常见问题]
    原题|UnravellingbinaryarithmeticoperationsinPython作者|BrettCannon译者|豌豆花下猫(“Python猫 ... [详细]
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社区 版权所有