因为[]
和{}
是字面量语法,所以Python可以创建字节码来创建列表或字典对象:
>>> import dis
>>> dis.dis(compile('[]', '', 'eval'))
1 0 BUILD_LIST 0
3 RETURN_VALUE
>>> dis.dis(compile('{}', '', 'eval'))
1 0 BUILD_MAP 0
3 RETURN_VALUE
list()
和dict()
是不同的对象。它们的名称需要被解析,堆栈必须参与推送参数,帧必须被存储以便以后检索,并且需要进行调用。这一切都需要更多的时间。
对于空情况而言,至少需要一个LOAD_NAME
(需要搜索全局命名空间以及builtins
模块),然后是一个CALL_FUNCTION
,它必须保留当前帧:
>>> dis.dis(compile('list()', '', 'eval'))
1 0 LOAD_NAME 0 (list)
3 CALL_FUNCTION 0
6 RETURN_VALUE
>>> dis.dis(compile('dict()', '', 'eval'))
1 0 LOAD_NAME 0 (dict)
3 CALL_FUNCTION 0
6 RETURN_VALUE
你可以使用 timeit
单独计时名称查找:
>>> import timeit
>>> timeit.timeit('list', number=10**7)
0.30749011039733887
>>> timeit.timeit('dict', number=10**7)
0.4215109348297119
那里的时间差可能是由于字典哈希冲突所致。从调用这些对象的时间中减去这些时间,将结果与使用文字的时间进行比较:
>>> timeit.timeit('[]', number=10**7)
0.30478692054748535
>>> timeit.timeit('{}', number=10**7)
0.31482696533203125
>>> timeit.timeit('list()', number=10**7)
0.9991960525512695
>>> timeit.timeit('dict()', number=10**7)
1.0200958251953125
因此,每调用一次对象会额外增加1.00 - 0.31 - 0.30 == 0.39
秒的时间,每1千万次调用增加一次。
您可以通过将全局名称别名为本地名称(使用timeit
设置,绑定到名称的所有内容都是本地变量)来避免全局查找成本:
>>> timeit.timeit('_list', '_list = list', number=10**7)
0.1866450309753418
>>> timeit.timeit('_dict', '_dict = dict', number=10**7)
0.19016098976135254
>>> timeit.timeit('_list()', '_list = list', number=10**7)
0.841480016708374
>>> timeit.timeit('_dict()', '_dict = dict', number=10**7)
0.7233691215515137
但你永远无法克服那个CALL_FUNCTION
的成本。
()
和''
是特殊的,因为它们不仅为空,而且是不可变的,因此将它们作为单例是一个易于实现的优化;它们甚至不会创建新的对象,只是加载用于表示空元组/空字符串的单例。虽然这在技术上是一个实现细节,但我很难想象为什么它们不会出于性能原因缓存空的元组/空字符串。所以你之前关于[]
和{}
返回存储字面量的设想是错误的,但是对于()
和''
这一点是正确的。 - ShadowRanger{}
жҜ”и°ғз”Ёset()
жӣҙеҝ«пјҹ - cs95