我最近比较了[]
和list()
的处理速度,并惊讶地发现[]
运行的速度比{{1}快 }。我使用list()
和{}
进行了相同的测试,结果几乎完全相同:dict()
和[]
两者都花费了大约0.128秒/百万个周期,而{}
并且list()
每个大约需要0.428秒/百万次。
这是为什么? dict()
和[]
(以及可能还有{}
和()
)会立即传回一些空库存文字的副本,而其明确命名的对应文件({{1} },''
,list()
,dict()
)完全去创建一个对象,无论它们是否真的有元素?
我不知道这两种方法有何不同,但我很想知道。 我在文档中或在SO上找不到答案,搜索空括号结果比我预期的问题更多。
我通过调用tuple()
和str()
以及timeit.timeit("[]")
和timeit.timeit("list()")
来分别比较列表和词典,从而获得了时间结果。我正在运行Python 2.7.9。
我最近发现“Why is if True slower than if 1?”比较了timeit.timeit("{}")
与timeit.timeit("dict()")
的效果,似乎触及了类似的字面与全局情景;也许它值得考虑。
答案 0 :(得分:707)
因为[]
和{}
是文字语法。 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
(必须搜索全局命名空间以及__builtin__
module)后跟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
因此,必须调用该对象每1000万次呼叫需要额外1.00 - 0.31 - 0.30 == 0.39
秒。
您可以通过将全局名称别名为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
但你永远无法克服CALL_FUNCTION
费用。
答案 1 :(得分:138)
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
答案 2 :(得分:73)
因为SELECT
t.error_code
, t.error_description
, t.code
FROM temp_soap_monitoring_topup sm
, XMLTABLE(
XMLNAMESPACES (
'http://schemas.xmlsoap.org/soap/envelope/' AS "soapenv",
'http://service.soap.CDRator.com' as "ns",
'http://core.data.soap.CDRator.com/xsd' as "ax2130",
'http://webshop.result.service.soap.CDRator.com/xsd' as "ax2147",
'http://core.signup.data.soap.CDRator.com/xsd' as "ns3",
'http://service.soap.CDRator.com' as "ns5",
'http://payment.result.service.soap.CDRator.com/xsd' as "ax230",
'http://core.data.soap.CDRator.com/xsd' as "ax233",
'http://core.result.service.soap.CDRator.com/xsd' as "ax232"
)
, 'soapenv:Envelope/soapenv:Body/ns:updateRechargeTicketResponse/ns:return'
PASSING XMLTYPE(sm.response_xml)
COLUMNS
error_code VARCHAR2(100) PATH 'ax232:code/text()'
, error_description VARCHAR2(100) PATH 'ax232:description/text()'
, code VARCHAR2(100) PATH 'ax230:rechargeTicket/ax233:code/text()'
) t
是要将字符串转换为列表对象的function,而list
用于创建一个关键字列表。试试这个(可能对你更有意义):
[]
虽然
x = "wham bam"
a = list(x)
>>> a
["w", "h", "a", "m", ...]
为您提供包含您放入其中的任何内容的实际列表。
答案 3 :(得分:18)
这里的答案很棒,非常重要,完全涵盖了这个问题。对于那些感兴趣的人,我将从字节码中再下一步。我使用了最新的CPython回购;旧版本在这方面表现相似,但可能会有细微的变化。
以下是BUILD_LIST
的{{1}}和[]
CALL_FUNCTION
的执行细分。
BUILD_LIST
instruction: 你应该只看恐怖:
list()
我知道,非常复杂。这很简单:
PyList_New
创建一个新列表(这主要为新列表对象分配内存),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();
表示堆栈中的参数数量。直截了当。oparg
没有出错。 PyList_SET_ITEM
(宏)添加位于堆栈上的任何参数(在我们的示例中,这不是执行)。难怪它快!它是为创建新列表而定制的,没有别的: - )
CALL_FUNCTION
instruction: 这是您在查看代码处理if (list==NULL)
时看到的第一件事:
CALL_FUNCTION
看起来很无害,对吧?嗯,不,不幸的是,call_function
并不是一个直接调用该函数的直截了当的人,它不能。相反,它从堆栈中抓取对象,抓取堆栈的所有参数,然后根据对象的类型进行切换;它是一个:
PyCFunction_Type
?不,它是PyObject **sp, *res;
sp = stack_pointer;
res = call_function(&sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
goto error;
}
DISPATCH();
,list
不属于list
PyMethodType
?不,见上一页。PyFunctionType
?不,见上一页。我们正在调用PyCFunction
类型,传递给list
的参数为PyList_Type
。 CPython现在必须调用泛型函数来处理任何名为_PyObject_FastCallKeywords
的可调用对象,并且更多函数调用。
这个函数再次对某些函数类型进行了一些检查(我无法理解为什么)然后,在为kwargs 创建一个dict后,如果需要,继续调用_PyObject_FastCallDict
。< / p>
call_function
终于让我们到了某个地方!执行了更多检查后,_PyObject_FastCallDict
我们传入了type
,即抓取type.tp_call
。然后,它继续使用_PyStack_AsTuple
传递的参数创建一个元组,最后, grabs the tp_call
slot from the type
!
tp_call
接管并最终创建列表对象。它调用与type.__call__
对应的列表__new__
,并为PyType_GenericNew
分配内存:这实际上是它追赶PyList_New
的部分,最后< / em>的。所有以前的都是以通用方式处理对象所必需的。
最后,type_call
调用list.__init__
并使用任何可用的参数初始化列表,然后我们以我们来的方式返回。 : - )
最后,请记住LOAD_NAME
,这是另一个在此做出贡献的人。
很容易看出,在处理我们的输入时,Python通常必须跳过箍以实际找到适当的C
函数来完成这项工作。它没有立即调用它的简单性,因为它是动态的,有人可能会屏蔽list
(和男孩做很多人做)并且必须采取另一条路径。
这是list()
失去的地方:探索Python需要做的是找出它应该做些什么。
另一方面,字面语法意味着一件事;它不能改变,总是以预先确定的方式行事。
脚注:所有功能名称可能会从一个版本更改为另一个版本。这一点仍然存在,并且很可能会出现在任何未来的版本中,它是动态查找,会减慢速度。