Python:为什么迭代列表要比迭代xrange()生成器定义其长度更快?

Python:为什么迭代列表要比迭代xrange()生成器定义其长度更快?,python,microbenchmark,Python,Microbenchmark,为什么在列表上迭代要比在列表长度上定义的xrange生成器上迭代快 输入: timeit.timeit('for _ in dummy: continue', setup='dummy=xrange(10000)', number=100000) timeit.timeit('for _ in dummy: continue', setup='dummy = [0] * 10000', number=100000) 输出: 22.131134033203125 17.9161019325256

为什么在列表上迭代要比在列表长度上定义的xrange生成器上迭代快

输入:

timeit.timeit('for _ in dummy: continue', setup='dummy=xrange(10000)', number=100000)
timeit.timeit('for _ in dummy: continue', setup='dummy = [0] * 10000', number=100000)
输出:

22.131134033203125
17.916101932525635

我假设这取决于在预编译的C代码中以本机方式执行的这些操作的数量之比。

如果我不得不猜测这是因为比较是昂贵的操作。如果
xrange
看起来像这样:

def xrange(limit):
    counter = 0
    while counter < limit:
        counter += 1
def X范围(限制):
计数器=0
当计数器<限制时:
计数器+=1
那么你说的是10000个比较。与迭代列表相反,它只需在列表末尾提升
StopIteration


但是我不确定内部结构,所以我可能是错的。

我在Python3中做了,但是同样的结果出现了。我将
范围
创建放在
设置
中,以便进行更准确的比较

In [1]: timeit.timeit('for _ in a: continue', setup='a=list(range(10000))', number=10000)
Out[1]: 1.195666481000444

In [2]: timeit.timeit('for _ in a: continue', setup='a=range(10000)', number=10000)
Out[2]: 2.4083170039994
我认为主要的区别在于
range
在每次迭代时都会懒洋洋地生成
值,而如果使用列表,它只需要从内存中读取它们。比照

In [3]: timeit.timeit('for _ in range(10000): continue', number=10000)
Out[3]: 4.166428555001403

In [4]: timeit.timeit('for _ in list(range(10000)): continue', number=10000)
Out[4]: 5.800707030000922

我们将创建对象所需的时间考虑在内。它显示了惰性计算的点。

当我们循环一个已经创建的列表时,我们实际上是在迭代它的迭代器并每次调用它的
next
方法,它只是根据它维护的内部索引()从列表中生成下一项。i、 e此处不创建新对象。另一方面,Python 3中的
range()
或Python 2中的
xrange()
在每次迭代期间,Python都必须这样做,这可能会很昂贵:

>>> timeit.timeit('for _ in dummy: continue', setup='dummy = xrange(10**4)', number=100000)
8.74455213546753
>>> timeit.timeit('for _ in dummy: continue', setup='dummy = [0] * 10000', number=100000)
7.1642138957977295
如果我们使用
itertools.repeat
None
来代替
xrange()
,我们会得到一些轻微的改进,因为现在我们在每次迭代中都不创建新对象,只需重复相同的对象

>>> timeit.timeit('for _ in repeat(None, 10000): continue', setup='from itertools import repeat', number=100000)
6.986715793609619
来自雷蒙德·赫廷格的:

使用
itertools更快。重复(无,次)
以控制 循环数(这可避免在上创建新的未使用的整数对象) 每次迭代)


当您使用
xrange
时,我假设Python2.x。实验的准确设置很重要

使用虚拟变量(xrange较慢)
13.719306168122216

15.667266362411738

没有虚拟变量(X范围更快) 但是,如果我们从图片中取出虚拟变量:

timeit.timeit('for _ in range(10000): continue', number=100000)
20.79111238831547

15.494247599682467

为什么? 虚拟变量差异

这表明
xrange
变量的设置成本较低,但迭代成本稍高。在第一个实例中,您只设置了一次对象,但迭代了100000次。在第二种情况下,设置100000次,每次迭代一次。有趣-由于
xrange
的文档会让您相信相反的观点(我的重点是):

与range()类似,但不是返回列表,而是返回 根据需要生成范围内的数字对于循环,这是 略快于range(),内存效率更高。

代码差异

看一看,我们看到:

。。。
/***********************X范围迭代器**************************/
类型定义结构{
皮尤头
长指数;
长起点;
长步走;
龙伦;
}rangeiterobject;
静态PyObject*
rangeiter_下一个(rangeiterobject*r)
{
if(r->indexlen)
从long返回PyInt_(r->start+(r->index++)*r->step);
返回NULL;
}
...
因此,每次从
xrange
获取下一个号码的调用都会产生如下比较结果:

编辑


注-我使用
range(10000)
xrange(10000)
进行对比,但是,使用OP的
[0]*10000
也可以得到结果

那么[0]*10000中的
对于uuuuuuuu来说如何呢?
dummy=xrange(10000)
dummy=range(10000)
不是更公平的比较吗?否则,您将创建一个
xrange
对象N次,但只创建一次列表。@khelwood,应该是这样的。但最终,
range(10000)
将比
xrange(10000)
快,至少在python2.7中是这样。@no_name,使用python2.7,使用
setup='dummy=xrange(10000)
我得到0.797435998916626s,使用
setup='dummy=range(10000)
,我得到0.65576481915283s。@khelwood
xrange()
只是一个迭代器,创建一个迭代器或生成器几乎不需要纳秒,但在任何迭代真正开始之前,OTOH
range()
必须首先在内存中创建一个完整的列表。的确,@no\u name,尽管这不会有太大区别(因为您只是用
start
stop
替换0和1)。如果它真的有那么大的不同,至少我会感到非常惊讶。@wflynny我尝试了这两种语言的设置代码,结果相似-
xrange
(或者py3中的
range
)比在集合上迭代稍微慢一些。但引发StopIteration的代码也必须检查数组的长度。是否确实检查了长度?我只是在推理,我不知道它的来源,但是如果Python列表是用C实现的,并且实际上是一个链表,那么对列表的迭代就是用C进行比较,在
下一步
上检查
null
。这比在Python中发生的任何事情都要快得多。@WayneWerner-Python,它们只是数组。这就是索引速度如此之快的原因。说到比较,它们也发生在list的方法中:然而,从内存中读取要比完全在寄存器(xrange)中完成的事情更昂贵(即使预取到缓存也是如此)。@marcorossi你确定xrange完成得很复杂吗
timeit.timeit('for _ in dummy: continue', setup='dummy = xrange(10000)', number=100000)
timeit.timeit('for _ in range(10000): continue', number=100000)
timeit.timeit('for _ in xrange(10000): continue', number=100000)
...

/*********************** Xrange Iterator **************************/

typedef struct {
    PyObject_HEAD
    long        index;
    long        start;
    long        step;
    long        len;
} rangeiterobject;

static PyObject *
rangeiter_next(rangeiterobject *r)
{
    if (r->index < r->len)
        return PyInt_FromLong(r->start + (r->index++) * r->step);
    return NULL;
}

...