Why is [] faster than list()?
我最近比较了
为什么会这样?
我不知道这两种方法有什么不同,但我想知道。我在文件中找不到答案,找空括号的问题比我想象的要多。
我通过调用
我最近发现"为什么如果真的比1慢?"这比较了
因为
1 2 3 4 5 6 7 | >>> 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 |
对于空的情况,这意味着您至少有一个
1 2 3 4 5 6 7 8 | >>> 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 |
您可以使用
1 2 3 4 5 | >>> import timeit >>> timeit.timeit('list', number=10**7) 0.30749011039733887 >>> timeit.timeit('dict', number=10**7) 0.4215109348297119 |
时间差异可能存在字典哈希冲突。从调用这些对象的次数中减去这些次数,并将结果与使用文本的次数进行比较:
1 2 3 4 5 6 7 8 | >>> 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 |
因此,每1000万次调用需要额外的
您可以通过将全局名称命名为本地名称来避免全局查找成本(使用
1 2 3 4 5 6 7 8 | >>> 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 |
但你永远无法克服这一点。
1 2 3 4 5 6 7 8 9 10 11 | 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 |
因为
1 2 3 4 | x ="wham bam" a = list(x) >>> a ["w","h","a","m", ...] |
同时
1 2 3 | y = ["wham bam"] >>> y ["wham bam"] |
给你一个包含你放在里面的任何东西的实际列表。
这里的答案是很好的,就这点而言,并完全涵盖了这个问题。对于那些感兴趣的人,我将从字节代码中进一步降低一步。我正在使用最新的cpython回购;在这方面,旧版本的行为类似,但可能会有细微的变化。
以下是对每一项的执行情况的分解,
你应该看看恐怖:
1 2 3 4 5 6 7 8 9 | 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(); |
我知道,非常复杂。这是多么简单:
- 用
PyList_New 创建一个新的列表(这主要是为一个新的列表对象分配内存),oparg 表示堆栈上的参数数量。直奔主题。 - 检查确认
if (list==NULL) 没有问题。 - 添加位于具有
PyList_SET_ITEM 的堆栈(宏)上的任何参数(在本例中,这不是执行的)。
难怪速度太快了!它是为创建新列表而定制的,没有其他内容:—)
下面是您在查看代码处理
1 2 3 4 5 6 7 8 9 | PyObject **sp, *res; sp = stack_pointer; res = call_function(&sp, oparg, NULL); stack_pointer = sp; PUSH(res); if (res == NULL) { goto error; } DISPATCH(); |
看起来很无害,对吧?不,不幸的是不是,
PyCFunction_Type 号?不,它是list ,list 不是PyCFunction 型。PyMethodType 号?不,见前面。PyFunctionType 号?不,看前面。
我们称之为
此函数再次检查某些函数类型(我无法理解原因),然后在为Kwargs创建dict(如果需要)后,继续调用
与
最后,
最后,Remmeber the
很容易看出,在处理我们的输入时,python通常必须跳过Hoops,以便实际找到合适的
这就是
另一方面,字面句法只意味着一件事;它不能被改变,并且总是以预先确定的方式运行。
footnote:all function name are subject to change from one one to the other.这一点仍然存在,而且很可能在未来的任何版本中都存在,是动态查找减慢了速度。
Why is
[] faster thanlist() ?
最大的原因是,python将
它立即使用
我的解释试图给你这个直觉。
解释在语法中,这被称为"列表显示"。来自文档:
A list display is a possibly empty series of expressions enclosed in
square brackets:
1 list_display ::= "[" [starred_list | comprehension]"]"A list display yields a new list object, the contents being specified
by either a list of expressions or a comprehension. When a
comma-separated list of expressions is supplied, its elements are
evaluated from left to right and placed into the list object in that
order. When a comprehension is supplied, the list is constructed from
the elements resulting from the comprehension.
简而言之,这意味着创建了
没有规避这一点——这意味着Python可以尽可能快地完成它。
另一方面,通过使用builtin list构造函数创建一个内置的
例如,假设我们希望大声创建列表:
1 2 3 4 5 6 7 | class List(list): def __init__(self, iterable=None): if iterable is None: super().__init__() else: super().__init__(iterable) print('List initialized.') |
然后我们可以截取模块级全局作用域上的名称
1 2 3 4 5 | >>> list = List >>> a_list = list() List initialized. >>> type(a_list) <class '__main__.List'> |
同样,我们可以从全局命名空间中删除它
1 | del list |
并将其放入内置命名空间:
1 2 | import builtins builtins.list = List |
现在:
1 2 3 4 | >>> list_0 = list() List initialized. >>> type(list_0) <class '__main__.List'> |
请注意,列表显示无条件地创建列表:
1 2 3 | >>> list_1 = [] >>> type(list_1) <class 'list'> |
我们可能只是暂时这样做,所以让我们撤消更改—首先从内置中删除新的
1 2 3 4 5 6 7 8 9 | >>> 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 |
哦,不,我们失去了对原版的追踪。
不用担心,我们仍然可以得到
1 2 3 | >>> builtins.list = type([]) >>> list() [] |
所以…
Why is
[] faster thanlist() ?
正如我们所看到的,我们可以覆盖
然后我们必须打电话给任何我们查过的电话。从语法:
A call calls a callable object (e.g., a function) with a possibly
empty series of arguments:
1 call ::= primary"(" [argument_list [","] | comprehension]")"
我们可以看到,它对任何名称都做同样的事情,而不仅仅是列出:
1 2 3 4 5 6 7 8 9 | >>> 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 |
对于
1 2 3 | >>> dis.dis('[]') 1 0 BUILD_LIST 0 2 RETURN_VALUE |
它直接构建列表,而不需要在字节码级别进行任何查找或调用。
结论我们已经证明,使用作用域规则可以使用用户代码截取
而