Python 有序的字典,更快迭代速度和更紧凑的数据结构
原文链接:https://mail.python.org/pipermail/python-dev/2012-December/123028.html
Python3.6 之后字典的数据结构已修改为如下结构。
字典的当前内存布局
不必要地低效。它有一个
包含散列值,键指针
和值指针的24字节条目的稀疏表。
相反,24字节条目应存储在
由稀疏索引表引用的密集表中。
比如,如下一个字典:
d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}
Python<=3.5 这样存储:
entries = [['--', '--', '--'],
[-8522787127447073495, 'barry', 'green'],
['--', '--', '--'],
['--', '--', '--'],
['--', '--', '--'],
[-9092791511155847987, 'timmy', 'red'],
['--', '--', '--'],
[-6480567542315338377, 'guido', 'blue']]
相反,更合理的存储结构如下Python>=3.6:
indices = [None, 1, None, None, None, 0, None, 2]
entries = [[-9092791511155847987, 'timmy', 'red'],
[-8522787127447073495, 'barry', 'green'],
[-6480567542315338377, 'guido', 'blue']]
只需要更改数据布局。哈希表
算法将保持不变。
将保留所有当前优化,包括密钥共享
dicts和仅字符串dicts的自定义查找功能
。哈希函数没有变化,
节省的内存很大(
压缩率从30%到95%,具体取决于表的填充程度)。
小的dicts(大小0,1或2)获得最大的好处。
对于具有n个条目的大小为t的稀疏表,大小为:
curr_size = 24 * t
new_size = 24 * n + sizeof(index) * t
在上面的timmy / barry / guido示例中,当前
大小为192字节(8 24字节条目)和新
大小是80字节(三个24字节条目加上八个
1字节索引)。这提供了58%的压缩。
注意,
对于小的dicts,sizeof(index)索引可以小到单个字节,对于较大的
dicts,sizeof索引 可以小到两个字节,对于巨大的dict,可以是sizeof(py_ssize_t)。
除了节省空间外,新的内存布局
使迭代更快。目前,keys(),values和
items()遍历稀疏表,跳过
哈希表中的空闲槽。现在,keys / values / items可以
使用更少的内存
访问直接在密集表上循环。
另一个好处是调整大小更快,
触及更少的内存。目前,
在
调整大小期间移动或复制每个散列/键/值条目。在新布局中,仅
更新索引。在大多数情况下,散列/键/值条目
永远不会移动(除了偶尔交换以填充
删除留下的空洞)。
随着内存占用减少,我们也可以期待
更好的缓存利用率
对于那些想要尝试设计的人来说,
这里有一个纯粹的Python概念验证:
http://code.activestate.com/recipes/578375
YMMV:请记住,上面的大小静态假定
使用64-构建位Py_ssize_t和64位指针。
其他
版本的空间节省百分比略有不同。另外,请注意,在许多应用中,
数据的大小决定了容器的大小(即
一桶水的重量主要是水,
而不是水桶)。
雷蒙德