您正在创建10krange()对象。这些需要一些时间来实现。然后,您还必须为这些10k对象创建iterator objects(以便for循环迭代这些值)。接下来,for循环通过调用结果迭代器上的^{} method来使用迭代器协议。后两个步骤也适用于列表上的for循环。在

但最重要的是,你在while循环测试中作弊。while循环只需运行一次,因为您永远不会将i重置回0(多亏了Jim Fasarakis Hilliard pointing that out)。实际上,您正在运行一个while循环,总共19999个比较;第一个测试运行10k比较,其余9999个测试运行一个比较。这种比较很快:>>> import timeit

>>> timeit.timeit('while i<10000: True; i+=1',setup='i=0', number=10000)

0.0008302750065922737

>>> (

... timeit.timeit('while i<10000: True; i+=1', setup='i=0', number=1) +

... timeit.timeit('10000 < 10000', number=9999)

... )

0.0008467709994874895

看看这些数字有多接近?在

我的机器有点快,所以让我们创建一个比较基准;这是在运行于OS X 10.12.5的Macbook Pro(Retina,15英寸,2015年年中)上使用的是3.6.1。我们还可以修复while循环以在测试中设置i = 0,而不是设置(只运行一次):

^{pr2}$

糟糕,所以一个正确运行的while实际上是较慢的这里有你的前提(和我的!)。在

我使用pass来避免回答关于引用对象的速度有多快的问题(它很快,但不重要)。我的计时要比你的机器快6倍。在

如果您想探索为什么迭代速度更快,可以从创建range()对象开始,在Python中为for循环的各个组件计时:>>> timeit.timeit('range(10000)', number=10000)

0.0036197409499436617

因此,创建10000个range()对象比运行一个迭代10k次的while循环需要更多的时间。range()对象的创建成本比整数高。在

这确实涉及全局名称查找,速度较慢,可以通过使用setup='_range = range'然后使用_range(1000)来加快查找速度;这样可以减少大约1/3的时间。在

接下来,为此创建一个迭代器;这里我将为^{} function使用一个本地名称,因为for循环不必执行哈希表查找,而是只访问C函数。当然,在二进制文件中对内存位置的硬编码引用要快得多:>>> timeit.timeit('_iter(r)', setup='_iter = iter; r = range(10000)', number=10000)

0.0009729859884828329

相当快,但是,它所需的时间与单个while循环迭代10k次相同。所以创建iterable对象很便宜。C实现更快。我们还没有迭代。在

最后,我们对迭代器对象调用__next__,次数为10k次。这同样是用C代码完成的,缓存了对内部C实现的引用,但是使用^{} object我们至少可以尝试来获得一个大致的数字:>>> timeit.timeit('n()', setup='from functools import partial; i = iter(range(10000)); n = partial(i.__next__)', number=10000) * 10000

7.759470026940107

天哪,对iter(range(1000)).__next__的10k乘以10k的调用所花费的时间比管理的for循环要多出近4倍;这说明了实际的C实现的效率有多高。在

但是,它确实说明了在C代码中循环要快得多,这就是为什么正确执行while循环实际上要慢得多;在字节码中求整数和进行布尔比较比在C代码中迭代range()要花更多的时间(CPU直接在CPU寄存器中进行递增和比较):>>> (

... timeit.timeit('9999 + 1', number=10000 ** 2) +

... timeit.timeit('9999 < 10000', number=10000 ** 2)

... )

3.695550534990616

正是这些操作使while循环慢了大约3秒。在

TLDR:实际上您没有正确地测试while循环。我早该注意到的。在