首页 > 解决方案 > 为什么从元组列表创建python dict比从kwargs慢3倍

问题描述

有几种方法可以在 python 中构造字典,例如:

keyvals = [('foo', 1), ('bar', 'bar'), ('baz', 100)]

dict(keyvals)

dkwargs = {'foo': 1, 'bar': 'bar', 'baz': 100}

dict(**dkwargs)

当你对这些进行基准测试时

In [0]: %timeit dict(keyvals)
667 ns ± 38 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [1]: %timeit dict(**dkwargs)
225 ns ± 7.09 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

您会看到第一种方法比第二种方法慢了近 3 倍。为什么是这样?

标签: pythonpython-3.xperformancebenchmarkingpython-internals

解决方案


dict(**kwargs)传入一个现成的字典,因此 Python 可以复制一个已经存在的内部结构。

另一方面,元组列表需要迭代、验证、散列并将结果放入一个新的空表中。这几乎没有那么快。

Python 字典以哈希表的形式实现,并随着时间的推移随着键的添加而动态增长;它们从小开始,随着需要的出现,构建一个新的、更大的哈希表,数据(键、值和哈希)被复制。这在 Python 代码中是不可见的,但调整大小需要时间。但是当你使用dict(**kwargs)(或dict(other_dict)CPython(你用来测试的默认 Python 实现)时,可以走一条捷径:从一个足够大的哈希表开始。你不能用一个元组序列做同样的技巧,因为你可以'不知道序列中是否没有重复的键。

更多细节见该类型的 C 源码dict,具体是dict_update_common实现(从 调用dict_init());this 调用PyDict_MergeFromSeq2()元组序列的情况,或者PyDict_Merge()在传入关键字参数时调用。

PyDict_MergeFromSeq2()函数遍历序列,测试每个结果以确保有两个元素,然后实质上调用.__setitem__(key, value)字典。这可能需要在某些时候调整字典的大小!

函数( PyDict_Merge()via dict_merge()) 专门检测是否传入了常规字典,然后执行一次调整内部结构大小的快速路径然后使用调用直接从原始字典中复制散列和结构insertdict()(按照override == 1路径,override如设置为1当目标字典为空时,对于dict(**kwargs)) 总是如此。只需调整一次大小并直接使用内部数据会快得多,需要做的工作要少得多!

所有这些都是特定于 CPython 的实现细节。其他 Python 实现,如 Jython、IronPython 和 PyPy 可以自行决定dict类型的内部如何工作,并且会显示相同操作的不同性能差异。


推荐阅读