在enumerate
使用start
指定的默认参数进行计时时,我注意到以下奇怪的行为:
In [23]: %timeit enumerate([1, 2, 3, 4]) The slowest run took 7.18 times longer than the fastest. This could mean that an intermediate result is being cached 1000000 loops, best of 3: 511 ns per loop In [24]: %timeit enumerate([1, 2, 3, 4], start=0) The slowest run took 12.45 times longer than the fastest. This could mean that an intermediate result is being cached 1000000 loops, best of 3: 1.22 µs per loop
因此,对于start
指定的情况,大约减少2倍.
为每种情况发出的字节代码并没有真正表明任何会导致速度显着差异的因素.例如,在使用dis.dis
发出的附加命令检查不同的调用之后:
18 LOAD_CONST 5 ('start') 21 LOAD_CONST 6 (0)
这些以及拥有CALL_FUNCTION
1个关键字是唯一的区别.
我尝试跟踪CPython
s中的调用ceval
,gdb
并且似乎都使用了do_call
,call_function
而不是我能检测到的其他一些优化.
现在,我理解enumerate
只是创建一个枚举迭代器,所以我们在这里处理对象创建(对吧?).我Objects/enumobject.c
试图发现任何差异如果start
被指定.(我相信)唯一不同的是start != NULL
发生以下情况:
if (start != NULL) { start = PyNumber_Index(start); if (start == NULL) { Py_DECREF(en); return NULL; } assert(PyInt_Check(start) || PyLong_Check(start)); en->en_index = PyInt_AsSsize_t(start); if (en->en_index == -1 && PyErr_Occurred()) { PyErr_Clear(); en->en_index = PY_SSIZE_T_MAX; en->en_longindex = start; } else { en->en_longindex = NULL; Py_DECREF(start); }
这看起来不像会导致2倍减速的东西.(我想,不确定.)
之前的代码段已在Python上执行3.5
,但类似的结果也存在2.x
.
这是我被困住的地方,无法弄清楚在哪里看.这可能只是在第二种情况下累积额外调用的开销,但同样,我不太确定.有谁知道这可能是什么原因?
一个原因可能是因为PyNumber_Index
在下一部分中指定了一个开始时调用:
if (start != NULL) {
start = PyNumber_Index(start);
如果您查看模块中的PyNumber_Index
函数,abstract.c
您将在函数的顶层看到以下注释:
/* Return a Python int from the object item.
Raise TypeError if the result is not an int
or if the object cannot be interpreted as an index.
*/
因此,此函数必须检查对象是否不能被解释为索引,并将返回相对错误.如果仔细查看源代码,您将看到所有这些检查和引用,特别是在下面的部分中,为了检查索引类型,必须进行嵌套结构取消引用:
result = item->ob_type->tp_as_number->nb_index(item);
if (result &&
!PyInt_Check(result) && !PyLong_Check(result)) {
...
需要花费很多时间来检查并返回期望结果.
但正如@ user2357112所提到的,另一个也是最重要的原因是因为python关键字参数匹配.
如果你把时间 - 它没有关键字参数的功能你会看到差异时间将减少约2倍的时间:
~$ python -m timeit "enumerate([1, 2, 3, 4])" 1000000 loops, best of 3: 0.251 usec per loop ~$ python -m timeit "enumerate([1, 2, 3, 4],start=0)" 1000000 loops, best of 3: 0.431 usec per loop ~$ python -m timeit "enumerate([1, 2, 3, 4],0)" 1000000 loops, best of 3: 0.275 usec per loop
与位置论证的区别在于:
>>> 0.251 - 0.275 -0.024
这似乎是因为PyNumber_Index
.