为什么从元组列表中创建python dict比从kwargs慢3倍

nar*_*eas 9 python performance benchmarking python-3.x python-internals

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

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

dict(keyvals)
Run Code Online (Sandbox Code Playgroud)

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

dict(**dkwargs)
Run Code Online (Sandbox Code Playgroud)

当你对这些基准进行测

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)
Run Code Online (Sandbox Code Playgroud)

你看到第一种方法比第二种方法快3倍.为什么是这样?

Mar*_*ers 14

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

另一方面,元组列表需要迭代,验证,散列并将结果插入到一个新的空表中.那不是那么快.

Python字典作为哈希表实现,并随着时间的推移随着密钥的增加而动态增长; 它们从小开始,随着需要的出现,构造了一个新的,更大的哈希表,数据(键,值和哈希)被复制.这在Python代码中都是不可见的,但调整大小需要时间.但是当你使用dict(**kwargs)(或dict(other_dict)CPython(你曾经测试过的默认Python实现)时可以采用一种快捷方式:从一个足够大的哈希表开始.你不能用一系列元组做同样的技巧,因为你可以如果序列中没有重复的密钥,请事先知道.

有关更多详细信息,请参阅类型的C源代码dict,特别是dict_update_common实现(从中调用dict_init()); 这会调用PyDict_MergeFromSeq2()序列的元组大小写,或者PyDict_Merge()在传入关键字参数时调用.

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

PyDict_Merge()(通过函数dict_merge())特异性检测,如果一个普通的字典被传入,然后执行快速路径该调整大小的内部结构一次,然后横跨直接使用来自原始词典中的散列值和结构拷贝insertdict()调用(遵循override == 1的路径,如override已经设置为1目标字典为空时,总是如此dict(**kwargs).只调整一次并直接使用内部数据要快得多,而且需要完成的工作要少得多!

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