Python 为什么 [] 比 list() 快?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30216000/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is [] faster than list()?
提问by Augusta
I recently compared the processing speeds of []
and list()
and was surprised to discover that []
runs more than three times fasterthan list()
. I ran the same test with {}
and dict()
and the results were practically identical: []
and {}
both took around 0.128sec / million cycles, while list()
and dict()
took roughly 0.428sec / million cycles each.
我最近比较了[]
和的处理速度,并list()
惊讶地发现[]
运行速度比list()
. 我跑了相同的测试与{}
和dict()
,结果几乎相同:[]
和{}
两个花了大约0.128sec /百万次,而list()
并dict()
把每个粗0.428sec /万次。
Why is this? Do []
and {}
(and probably ()
and ''
, too) immediately pass back a copies of some empty stock literal while their explicitly-named counterparts (list()
, dict()
, tuple()
, str()
) fully go about creating an object, whether or not they actually have elements?
为什么是这样?是否[]
和{}
(也可能是()
和''
)立即传回一些空的股票文字的副本,而它们明确命名的副本(list()
, dict()
, tuple()
, str()
)则完全创建一个对象,无论它们是否真的有元素?
I have no idea how these two methods differ but I'd love to find out. I couldn't find an answer in the docs or on SO, and searching for empty brackets turned out to be more problematic than I'd expected.
我不知道这两种方法有何不同,但我很想知道。我在文档或 SO 上找不到答案,结果发现搜索空括号比我预期的要麻烦。
I got my timing results by calling timeit.timeit("[]")
and timeit.timeit("list()")
, and timeit.timeit("{}")
and timeit.timeit("dict()")
, to compare lists and dictionaries, respectively. I'm running Python 2.7.9.
我通过调用timeit.timeit("[]")
andtimeit.timeit("list()")
和timeit.timeit("{}")
andtimeit.timeit("dict()")
分别比较列表和字典来获得计时结果。我正在运行 Python 2.7.9。
I recently discovered "Why is if True slower than if 1?" that compares the performance of if True
to if 1
and seems to touch on a similar literal-versus-global scenario; perhaps it's worth considering as well.
我最近发现了“为什么 if True 比 if 1 慢?”它比较了if True
to的性能,if 1
似乎触及了类似的文字与全局场景;也许这也值得考虑。
采纳答案by Martijn Pieters
Because []
and {}
are literal syntax. Python can create bytecode just to create the list or dictionary objects:
因为[]
和{}
是字面语法。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()
and dict()
are separate objects. Their names need to be resolved, the stack has to be involved to push the arguments, the frame has to be stored to retrieve later, and a call has to be made. That all takes more time.
list()
并且dict()
是单独的对象。它们的名称需要解析,必须涉及堆栈以推送参数,必须存储帧以供稍后检索,并且必须进行调用。这一切都需要更多的时间。
For the empty case, that means you have at the very least a LOAD_NAME
(which has to search through the global namespace as well as the __builtin__
module) followed by a CALL_FUNCTION
, which has to preserve the current frame:
对于空的情况,这意味着您至少有 a LOAD_NAME
(必须搜索全局命名空间以及__builtin__
module)后跟 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
You can time the name lookup separately with timeit
:
您可以使用以下命令单独计时名称查找timeit
:
>>> import timeit
>>> timeit.timeit('list', number=10**7)
0.30749011039733887
>>> timeit.timeit('dict', number=10**7)
0.4215109348297119
The time discrepancy there is probably a dictionary hash collision. Subtract those times from the times for calling those objects, and compare the result against the times for using literals:
时间差异可能是字典哈希冲突。从调用这些对象的时间中减去这些时间,并将结果与使用文字的时间进行比较:
>>> 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
So having to call the object takes an additional 1.00 - 0.31 - 0.30 == 0.39
seconds per 10 million calls.
因此,1.00 - 0.31 - 0.30 == 0.39
每 1000 万次调用,必须调用该对象需要额外的秒数。
You can avoid the global lookup cost by aliasing the global names as locals (using a timeit
setup, everything you bind to a name is a local):
您可以通过将全局名称别名为本地名称来避免全局查找成本(使用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
but you never can overcome that CALL_FUNCTION
cost.
但你永远无法克服这个CALL_FUNCTION
成本。
回答by Torxed
Because list
is a functionto convert say a string to a list object, while []
is used to create a list off the bat. Try this (might make more sense to you):
因为list
是一个功能转化说一个字符串列表对象,而[]
用于创建一个列表蝙蝠。试试这个(可能对你更有意义):
x = "wham bam"
a = list(x)
>>> a
["w", "h", "a", "m", ...]
While
尽管
y = ["wham bam"]
>>> y
["wham bam"]
Gives you a actual list containing whatever you put in it.
为您提供一个包含您放入其中的任何内容的实际列表。
回答by Dan D.
list()
requires a global lookup and a function call but []
compiles to a single instruction. See:
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
回答by Dimitris Fasarakis Hilliard
The answers here are great, to the point and fully cover this question. I'll drop a further step down from byte-code for those interested. I'm using the most recent repo of CPython; older versions behave similar in this regard but slight changes might be in place.
这里的答案很好,切中要害,并且完全涵盖了这个问题。对于那些感兴趣的人,我将进一步降低字节码。我正在使用最新的 CPython 存储库;旧版本在这方面的行为类似,但可能会有细微的变化。
Here's a break down of the execution for each of these, BUILD_LIST
for []
and CALL_FUNCTION
for list()
.
以下是对每个BUILD_LIST
for[]
和CALL_FUNCTION
for的执行分解list()
。
The BUILD_LIST
instruction:
该BUILD_LIST
指令:
You should just view the horror:
你应该只看恐怖:
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();
Terribly convoluted, I know. This is how simple it is:
非常复杂,我知道。这是多么简单:
- Create a new list with
PyList_New
(this mainly allocates the memory for a new list object),oparg
signalling the number of arguments on the stack. Straight to the point. - Check that nothing went wrong with
if (list==NULL)
. - Add any arguments (in our case this isn't executed) located on the stack with
PyList_SET_ITEM
(a macro).
- 创建一个新列表
PyList_New
(这主要为新列表对象分配内存),oparg
指示堆栈上的参数数量。开门见山。 - 检查是否没有任何问题
if (list==NULL)
。 - 使用
PyList_SET_ITEM
(宏)添加位于堆栈上的任何参数(在我们的例子中,这不会被执行)。
No wonder it is fast! It's custom-made for creating new lists, nothing else :-)
怪不得这么快!它是为创建新列表而定制的,仅此而已:-)
The CALL_FUNCTION
instruction:
该CALL_FUNCTION
指令:
Here's the first thing you see when you peek at the code handling 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();
Looks pretty harmless, right? Well, no, unfortunately not, call_function
is not a straightforward guy that will call the function immediately, it can't. Instead, it grabs the object from the stack, grabs all arguments of the stack and then switches based on the type of the object; is it a:
看起来很无害,对吧?好吧,不,不幸的是不是,call_function
不是一个会立即调用该函数的直截了当的人,它不能。相反,它从堆栈中抓取对象,抓取堆栈的所有参数,然后根据对象的类型进行切换;是不是:
PyCFunction_Type
? Nope, it islist
,list
isn't of typePyCFunction
PyMethodType
? Nope, see previous.PyFunctionType
? Nopee, see previous.
PyCFunction_Type
? 不,它是list
,list
不是类型PyCFunction
PyMethodType
? 没有,请看前面。PyFunctionType
? 不,见前文。
We're calling the list
type, the argument passed in to call_function
is PyList_Type
. CPython now has to call a generic function to handle any callable objects named _PyObject_FastCallKeywords
, yay more function calls.
我们正在调用list
类型,传入的参数call_function
是PyList_Type
。CPython 现在必须调用一个通用函数来处理任何名为 的可调用对象_PyObject_FastCallKeywords
,还有更多的函数调用。
This function again makes some checks for certain function types (which I cannot understand why) and then, after creating a dict for kwargs if required, goes on to call _PyObject_FastCallDict
.
这个函数再次对某些函数类型进行一些检查(我不明白为什么),然后,如果需要的话,在为 kwargs 创建一个 dict 之后,继续调用_PyObject_FastCallDict
.
_PyObject_FastCallDict
finally gets us somewhere! After performing even more checksit grabs the tp_call
slot from the type
of the type
we've passed in, that is, it grabs type.tp_call
. It then proceeds to create a tuple out of of the arguments passed in with _PyStack_AsTuple
and, finally, a call can finally be made!
_PyObject_FastCallDict
终于把我们带到了某个地方!执行后甚至更多的检查它抓住了tp_call
从插槽type
中的type
我们在通过了,那就是它抓住type.tp_call
。然后它继续从传入的参数中创建一个元组_PyStack_AsTuple
,最后,终于可以进行调用了!
tp_call
, which matches type.__call__
takes over and finally creates the list object. It calls the lists __new__
which corresponds to PyType_GenericNew
and allocates memory for it with PyType_GenericAlloc
: This is actually the part where it catches up with PyList_New
, finally. All the previous are necessary to handle objects in a generic fashion.
tp_call
,匹配type.__call__
接管并最终创建列表对象。它调用__new__
对应的列表PyType_GenericNew
并为它分配内存PyType_GenericAlloc
:这实际上是它追上的部分PyList_New
,finally。所有前面的都是以通用方式处理对象所必需的。
In the end, type_call
calls list.__init__
and initializes the list with any available arguments, then we go on a returning back the way we came. :-)
最后,使用任何可用的参数type_call
调用list.__init__
并初始化列表,然后我们继续返回我们来的方式。:-)
Finally, remmeber the LOAD_NAME
, that's another guy that contributes here.
最后,请记住LOAD_NAME
,这是另一个在这里做出贡献的人。
It's easy to see that, when dealing with our input, Python generally has to jump through hoops in order to actually find out the appropriate C
function to do the job. It doesn't have the curtesy of immediately calling it because it's dynamic, someone might mask list
(and boy do many people do) and another path must be taken.
很容易看出,在处理我们的输入时,Python 通常必须跳过障碍才能真正找到合适的C
函数来完成这项工作。它没有立即调用它的礼貌,因为它是动态的,有人可能会掩盖list
(很多人都会这样做),并且必须采取另一条道路。
This is where list()
loses much: The exploring Python needs to do to find out what the heck it should do.
这就是list()
损失很大的地方:探索 Python 需要做的事情是找出它应该做什么。
Literal syntax, on the other hand, means exactly one thing; it cannot be changed and always behaves in a pre-determined way.
另一方面,文字语法意味着一件事。它无法更改,并且始终以预先确定的方式运行。
Footnote: All function names are subject to change from one release to the other. The point still stands and most likely will stand in any future versions, it's the dynamic look-up that slows things down.
脚注:从一个版本到另一个版本,所有函数名称都可能发生变化。这一点仍然成立,并且很可能在任何未来版本中都会成立,动态查找会减慢速度。
回答by Aaron Hall
Why is
[]
faster thanlist()
?
为什么
[]
比 快list()
?
The biggest reason is that Python treats list()
just like a user-defined function, which means you can intercept it by aliasing something else to list
and do something different (like use your own subclassed list or perhaps a deque).
最大的原因是 Pythonlist()
就像一个用户定义的函数一样对待它,这意味着你可以通过给其他东西取别名来拦截它list
并做一些不同的事情(比如使用你自己的子类列表或双端队列)。
It immediately creates a new instance of a builtin list with []
.
它立即创建了一个内置列表的新实例[]
。
My explanation seeks to give you the intuition for this.
我的解释旨在为您提供对此的直觉。
Explanation
解释
[]
is commonly known as literal syntax.
[]
通常称为文字语法。
In the grammar, this is referred to as a "list display". From the docs:
在语法中,这被称为“列表显示”。从文档:
A list display is a possibly empty series of expressions enclosed in square brackets:
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.
列表显示是括在方括号中的一系列可能为空的表达式:
list_display ::= "[" [starred_list | comprehension] "]"
列表显示产生一个新的列表对象,其内容由表达式列表或推导式指定。当提供逗号分隔的表达式列表时,它的元素从左到右求值并按该顺序放入列表对象中。当提供了一个推导式时,列表是由推导式产生的元素构造的。
In short, this means that a builtin object of type list
is created.
简而言之,这意味着list
创建了一个内置类型的对象。
There is no circumventing this - which means Python can do it as quickly as it may.
无法绕过这一点 - 这意味着 Python 可以尽可能快地完成它。
On the other hand, list()
can be intercepted from creating a builtin list
using the builtin list constructor.
另一方面,list()
可以通过list
使用内置列表构造函数创建内置函数来拦截。
For example, say we want our lists to be created noisily:
例如,假设我们希望我们的列表被嘈杂地创建:
class List(list):
def __init__(self, iterable=None):
if iterable is None:
super().__init__()
else:
super().__init__(iterable)
print('List initialized.')
We could then intercept the name list
on the module level global scope, and then when we create a list
, we actually create our subtyped list:
然后我们可以list
在模块级别的全局范围内拦截名称,然后当我们创建 a 时list
,我们实际上创建了我们的子类型列表:
>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>
Similarly we could remove it from the global namespace
同样,我们可以将其从全局命名空间中删除
del list
and put it in the builtin namespace:
并将其放在内置命名空间中:
import builtins
builtins.list = List
And now:
现在:
>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>
And note that the list display creates a list unconditionally:
请注意,列表显示会无条件地创建一个列表:
>>> list_1 = []
>>> type(list_1)
<class 'list'>
We probably only do this temporarily, so lets undo our changes - first remove the new List
object from the builtins:
我们可能只是暂时这样做,所以让我们撤消我们的更改 - 首先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
Oh, no, we lost track of the original.
哦,不,我们忘记了原著。
Not to worry, we can still get list
- it's the type of a list literal:
不用担心,我们仍然可以得到list
- 它是列表文字的类型:
>>> builtins.list = type([])
>>> list()
[]
So...
所以...
Why is
[]
faster thanlist()
?
为什么
[]
比 快list()
?
As we've seen - we can overwrite list
- but we can't intercept the creation of the literal type. When we use list
we have to do the lookups to see if anything is there.
正如我们所见——我们可以覆盖list
——但我们不能拦截文字类型的创建。当我们使用时,list
我们必须进行查找以查看是否存在任何内容。
Then we have to call whatever callable we have looked up. From the grammar:
然后我们必须调用我们查找过的任何可调用对象。从语法上看:
A call calls a callable object (e.g., a function) with a possibly empty series of arguments:
call ::= primary "(" [argument_list [","] | comprehension] ")"
一个调用调用一个可调用对象(例如,一个函数),可能带有一系列空参数:
call ::= primary "(" [argument_list [","] | comprehension] ")"
We can see that it does the same thing for any name, not just list:
我们可以看到它对任何名称都做同样的事情,而不仅仅是列表:
>>> 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
For []
there is no function call at the Python bytecode level:
因为[]
在 Python 字节码级别没有函数调用:
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
It simply goes straight to building the list without any lookups or calls at the bytecode level.
它只是直接构建列表,无需在字节码级别进行任何查找或调用。
Conclusion
结论
We have demonstrated that list
can be intercepted with user code using the scoping rules, and that list()
looks for a callable and then calls it.
我们已经证明list
可以使用作用域规则通过用户代码拦截它,并list()
查找可调用对象然后调用它。
Whereas []
is a list display, or a literal, and thus avoids the name lookup and function call.
而[]
是列表显示或文字,从而避免名称查找和函数调用。