你正在创建10k个
range()
对象。这需要一些时间来生成。然后,你还需要为这10k个对象创建
迭代器对象(用于
for
循环遍历值)。接下来,
for
循环通过调用结果迭代器上的
__next__
方法使用迭代器协议。最后两个步骤也适用于列表上的
for
循环。
但最重要的是,你在作弊while循环测试。 while
循环只需要运行一次,因为你从不将 i
重置回 0
(感谢Jim Fasarakis Hilliard指出这一点)。 实际上,你通过总共19999次比较运行了一个 while
循环; 第一次测试运行了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
设置为不是设置(只运行一次):
>>> import timeit
>>> timeit.timeit('for i in range(10000): pass', number=10000)
1.9789885189966299
>>> timeit.timeit('i=0\nwhile i<10000: True
5.172155902953818
抱歉,所以一个正确运行的
while
实际上是
更慢的,你的前提(和我的)就这样消失了!我使用
pass
来避免回答关于引用该对象有多快的问题(它很快,但不是重点)。我的计时将比您的机器快6倍。如果您想探索为什么迭代更快,可以计算Python中
for
循环的各个组件的时间,从创建
range()
对象开始:
>>> timeit.timeit('range(10000)', number=10000)
0.0036197409499436617
因此创建10000个range()
对象需要比迭代10000次的单个while
循环更多的时间。range()
对象的创建比整数更昂贵。
这包括全局名称查找,这会更慢,您可以使用setup='_range = range'
来使其更快,然后使用_range(1000)
;这可以缩短约1/3的时间。
接下来,为此创建一个迭代器;在这里,我将使用iter()
函数的本地名称,因为for
循环不必进行哈希表查找,而是直接访问C函数。当然,对于二进制中内存位置的硬编码引用要快得多:
>>> timeit.timeit('_iter(r)', setup='_iter = iter; r = range(10000)', number=10000)
0.0009729859884828329
相当快,但是它需要与单个
while
循环迭代10k次花费同样的时间。因此创建可迭代对象是廉价的。C实现仍然更快。我们尚未进行迭代。
最后,我们在迭代器对象上调用
__next__
10k次。这再次是在C代码中完成的,具有对内部C实现的缓存引用,但是使用
functools.partial()
对象,我们至少可以
尝试得到一个大概的数字:
>>> 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次调用,而使用for
循环则只需要更短的时间;这说明实际的C语言实现非常高效。
然而,这也表明在C代码中循环速度更快,这就是为什么正确执行时,while
循环实际上比较慢的原因;在字节码中对整数求和和做布尔比较所需的时间比在C代码中迭代range()
所需的时间更长(在C代码中,CPU直接在CPU寄存器中进行递增和比较):
>>> (
... timeit.timeit('9999 + 1', number=10000 ** 2) +
... timeit.timeit('9999 < 10000', number=10000 ** 2)
... )
3.695550534990616
正是这些操作使得while
循环变慢了大约3秒钟。
TLDR:您实际上没有正确测试
while
循环。我早些时候应该注意到这一点。
range()
对象是有成本的,但成本并不是那么高。即使在 Python 2 中,我也无法让它一直运行到 12.7 秒。 - Martijn Pieters