当使用指定的默认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)

这些以及带有1个关键字的CALL_FUNCTION是唯一的区别。

我尝试通过CPython跟踪ceval s 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

关于python - 为什么未指定关键字start时枚举执行速度较慢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34676913/

10-16 21:40