Aug*_*sta 679 python performance list instantiation literals
我最近比较了处理速度[]和,list()并且惊讶地发现它的[]运行速度比它快三倍list().我跑了相同的测试与{}和dict(),结果几乎相同:[]和{}两个花了大约0.128sec /百万次,而list()和dict()大约花费每个0.428sec /万次.
为什么是这样?不要[]和{}(可能()和'',太)立即传回文字的一些空股票的份,而其明确命名同行(list(),dict(),tuple(),str())完全去创建一个对象,他们是否真的有元素?
我不知道这两种方法有何不同,但我很想知道.我无法在文档中或在SO上找到答案,搜索空括号结果比我预期的问题更多.
我拨打了我的时序结果timeit.timeit("[]")和timeit.timeit("list()"),和timeit.timeit("{}")和timeit.timeit("dict()"),分别比较列表和字典.我正在运行Python 2.7.9.
我最近发现的" 为什么是,如果真慢于如果为1? "来比较的性能if True来if 1,似乎触及了类似的文字,对全局的情况; 也许值得考虑一下.
Mar*_*ers 731
因为[]并且{}是字面语法.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
Run Code Online (Sandbox Code Playgroud)
list()并且dict()是单独的对象.需要解析它们的名称,必须涉及堆栈以推送参数,必须存储帧以便稍后检索,并且必须进行调用.这都需要更多时间.
对于空的情况,这意味着你至少有一个LOAD_NAME(必须搜索全局命名空间以及__builtin__模块)后面跟a 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
Run Code Online (Sandbox Code Playgroud)
您可以使用以下命令单独查找名称timeit:
>>> import timeit
>>> timeit.timeit('list', number=10**7)
0.30749011039733887
>>> timeit.timeit('dict', number=10**7)
0.4215109348297119
Run Code Online (Sandbox Code Playgroud)
时间差异可能存在字典哈希冲突.从调用这些对象的时间减去这些时间,并将结果与使用文字的时间进行比较:
>>> 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
Run Code Online (Sandbox Code Playgroud)
因此,必须调用该对象1.00 - 0.31 - 0.30 == 0.39每1000万次调用需要额外的秒数.
您可以通过将全局名称别名为locals来避免全局查找成本(使用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
Run Code Online (Sandbox Code Playgroud)
但你永远无法克服CALL_FUNCTION成本.
Dan*_* D. 143
list()需要全局查找和函数调用,但[]编译为单个指令.看到:
Python 2.7.3
>>> import dis
>>> print dis.dis(lambda: list())
1 0 LOAD_GLOBAL 0 (list)
3 CALL_FUNCTION 0
6 RETURN_VALUE
None
>>> print dis.dis(lambda: [])
1 0 BUILD_LIST 0
3 RETURN_VALUE
None
Run Code Online (Sandbox Code Playgroud)
Tor*_*xed 75
因为list是一个功能转化说一个字符串列表对象,而[]用于创建一个列表蝙蝠.试试这个(可能对你更有意义):
x = "wham bam"
a = list(x)
>>> a
["w", "h", "a", "m", ...]
Run Code Online (Sandbox Code Playgroud)
而
y = ["wham bam"]
>>> y
["wham bam"]
Run Code Online (Sandbox Code Playgroud)
为您提供包含您放入其中的任何内容的实际列表.
Jim*_*ard 19
这里的答案很棒,很重要,完全涵盖了这个问题.对于那些感兴趣的人,我会从字节码中再下一步.我正在使用最新的CPython回购; 旧版本在这方面表现相似,但可能会有细微的变化.
这里有一个休息的执行对于这些,下来BUILD_LIST了[],并CALL_FUNCTION为list().
BUILD_LIST指令:你应该只看恐怖:
PyObject *list = PyList_New(oparg);
if (list == NULL)
goto error;
while (--oparg >= 0) {
PyObject *item = POP();
PyList_SET_ITEM(list, oparg, item);
}
PUSH(list);
DISPATCH();
Run Code Online (Sandbox Code Playgroud)
我知道,非常复杂.这是多么简单:
PyList_New(这主要为新列表对象分配内存),oparg表示堆栈中的参数数量.开门见山.if (list==NULL).PyList_SET_ITEM(宏)添加位于堆栈上的任何参数(在我们的示例中,这不会被执行).难怪它快!它是为创建新列表而定制的,没有别的:-)
CALL_FUNCTION指令:这是您在查看代码处理时首先看到的内容CALL_FUNCTION:
PyObject **sp, *res;
sp = stack_pointer;
res = call_function(&sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
goto error;
}
DISPATCH();
Run Code Online (Sandbox Code Playgroud)
看起来很无害,对吧?嗯,不,不幸的call_function是,不是一个直接调用函数的直接人,它不能.相反,它从堆栈中抓取对象,抓取堆栈的所有参数,然后根据对象的类型进行切换; 它是一个:
PyCFunction_Type?不,它是list,list是类型的不PyCFunctionPyMethodType?不,见前一个.PyFunctionType?不,见前面.我们正在调用list类型,传入的参数call_function是PyList_Type.CPython现在必须调用泛型函数来处理任何名为的可调用对象_PyObject_FastCallKeywords,以及更多的函数调用.
这个函数再次对某些函数类型进行了一些检查(我无法理解为什么),然后,如果需要,在为kwargs创建一个dict之后,继续调用_PyObject_FastCallDict.
_PyObject_FastCallDict终于让我们到了某个地方!执行后甚至更多的检查 它抓住了tp_call从插槽中type的type,我们已经通过了,那就是它抓住type.tp_call.然后它继续从传入的参数中创建一个元组_PyStack_AsTuple,最后,最终可以进行调用!
tp_call,匹配type.__call__接管并最终创建列表对象.它调用与之__new__对应的列表PyType_GenericNew并为其分配内存PyType_GenericAlloc:这实际上是它赶上的部分PyList_New,最后.所有以前的都是以通用方式处理对象所必需的.
最后,type_call调用list.__init__并使用任何可用的参数初始化列表,然后我们继续返回我们的方式.:-)
最后,请记住LOAD_NAME,这是另一个在这里贡献的人.
很容易看出,在处理我们的输入时,Python通常必须跳过箍以实际找到适当的C函数来完成这项工作.它没有立即调用它的简单性,因为它是动态的,有人可能会掩盖list(男孩会做很多人这样做),必须采取另一条路径.
这就是list()失去的地方:探索Python需要做的是找出它应该做些什么.
另一方面,字面语法恰恰意味着一件事; 它不能改变,总是以预先确定的方式行事.
脚注:所有功能名称可能会从一个版本更改为另一个版本.这个观点仍然存在,而且很可能会出现在任何未来的版本中,它是动态的查找,可以减慢速度.
Aar*_*all 11
为什么
[]比快list()?
最大的原因是Python list()就像用户定义的函数一样,这意味着你可以通过别名来拦截它list并做一些不同的事情(比如使用你自己的子列表或者deque).
它立即创建一个内置列表的新实例[].
我的解释试图给你直觉.
[] 通常称为文字语法.
在语法中,这被称为"列表显示".来自文档:
列表显示是一个可能为空的系列表达式,用方括号括起来:
Run Code Online (Sandbox Code Playgroud)list_display ::= "[" [starred_list | comprehension] "]"列表显示产生一个新的列表对象,内容由表达式列表或理解指定.当提供以逗号分隔的表达式列表时,其元素将从左到右进行计算,并按该顺序放入列表对象中.当提供理解时,列表由理解产生的元素构成.
简而言之,这意味着list创建了一个内置的类型对象.
没有规避这个 - 这意味着Python可以尽可能快地完成它.
另一方面,list()可以list使用内置列表构造函数拦截创建内置函数.
例如,假设我们想要大声创建列表:
class List(list):
def __init__(self, iterable=None):
if iterable is None:
super().__init__()
else:
super().__init__(iterable)
print('List initialized.')
Run Code Online (Sandbox Code Playgroud)
然后,我们可以list在模块级全局范围内截取名称,然后在创建时list,我们实际创建了子类型列表:
>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>
Run Code Online (Sandbox Code Playgroud)
同样,我们可以从全局命名空间中删除它
del list
Run Code Online (Sandbox Code Playgroud)
并将其放在内置命名空间中:
import builtins
builtins.list = List
Run Code Online (Sandbox Code Playgroud)
现在:
>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>
Run Code Online (Sandbox Code Playgroud)
请注意,列表显示无条件地创建列表:
>>> list_1 = []
>>> type(list_1)
<class 'list'>
Run Code Online (Sandbox Code Playgroud)
我们可能只暂时执行此操作,因此请撤消我们的更改 - 首先List从内置中删除新对象:
>>> del builtins.list
>>> builtins.list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'builtins' has no attribute 'list'
>>> list()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'list' is not defined
Run Code Online (Sandbox Code Playgroud)
哦,不,我们失去了对原作的追踪.
不用担心,我们仍然可以得到list- 这是列表文字的类型:
>>> builtins.list = type([])
>>> list()
[]
Run Code Online (Sandbox Code Playgroud)
所以...
为什么
[]比快list()?
正如我们所见 - 我们可以覆盖list- 但我们不能拦截文字类型的创建.当我们使用时,list我们必须进行查找以查看是否有任何内容.
然后我们必须调用我们查找过的任何可调用对象.从语法:
调用使用可能为空的参数系列调用可调用对象(例如,函数):
Run Code Online (Sandbox Code Playgroud)call ::= primary "(" [argument_list [","] | comprehension] ")"
我们可以看到它对任何名称都做同样的事情,而不仅仅是列表:
>>> import dis
>>> dis.dis('list()')
1 0 LOAD_NAME 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
>>> dis.dis('doesnotexist()')
1 0 LOAD_NAME 0 (doesnotexist)
2 CALL_FUNCTION 0
4 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
因为[]在Python字节码级别没有函数调用:
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
它只是直接构建列表而没有在字节码级别进行任何查找或调用.
我们已经演示了list可以使用作用域规则拦截用户代码,并list()查找可调用然后调用它.
然后[]是列表显示或文字,从而避免名称查找和函数调用.