因此,关于for循环与while循环的速度的问题已经被问过很多次了。 for循环应该更快。
但是,当我在Python 3.5.1中对其进行测试时,结果如下:
timeit.timeit('for i in range(10000): True', number=10000)
>>> 12.697646026868842
timeit.timeit('while i<10000: True; i+=1',setup='i=0', number=10000)
>>> 0.0032265179766799434
while循环的运行速度比for循环快3000倍!我也尝试过为for循环预先生成一个列表:
timeit.timeit('for i in lis: True',setup='lis = [x for x in range(10000)]', number=10000)
>>> 3.638794646750142
timeit.timeit('while i<10000: True; i+=1',setup='i=0', number=10000)
>>> 0.0032454974941904524
这使得for循环快了3倍,但仍然相差3个数量级。
为什么会这样?
最佳答案
您正在创建10k个range()
对象。这些需要一些时间才能实现。然后,您还必须为那些10k对象创建iterator objects(为了使for
循环迭代这些值)。接下来,for
循环通过在生成的迭代器上调用__next__
method来使用迭代器协议。后两个步骤也适用于列表的for
循环。
但最重要的是,您在while
循环测试中作弊。 while
循环仅需运行一次,因为您无需将i
重设回0
(感谢Jim Fasarakis Hilliard pointing that out)。实际上,您正在通过总计19999个比较来运行while
循环;第一个测试进行1万次比较,其余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(视网膜,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; i+=1', number=10000)
5.172155902953818
糟糕,因此正确运行
while
的速度实际上要慢一些,前提是(我的!)。我使用
pass
来避免回答有关引用该对象的速度有多快的问题(这很快速,但很重要)。我的计时将比您的机器快6倍。如果您想探究为什么迭代会更快,可以在Python中为
for
循环的各个组件计时,从创建range()
对象开始:>>> timeit.timeit('range(10000)', number=10000)
0.0036197409499436617
因此,创建10000个
range()
对象比运行一个单独的while
循环要花10万倍的时间要多得多。与整数相比,创建range()
对象的成本更高。这确实涉及全局名称查找,这比较慢,您可以先使用
setup='_range = range'
然后使用_range(1000)
使其更快。大约刮掉了三分之一的时间。接下来,为此创建一个迭代器;在这里,我将为
iter()
function使用本地名称,因为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()
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__
所花的时间几乎是for
循环所管理的四倍;这表明了实际C实现的效率。但是,它的确说明了C代码中的循环要快得多,这就是为什么正确执行
while
循环实际上会更慢的原因。相加整数并在字节码中进行布尔比较比在C代码中遍历range()
所需的时间更多(CPU在CPU中直接在CPU寄存器中进行增量和比较):>>> (
... timeit.timeit('9999 + 1', number=10000 ** 2) +
... timeit.timeit('9999 < 10000', number=10000 ** 2)
... )
3.695550534990616
正是这些操作使
while
循环慢了大约3秒。TLDR:您实际上没有正确测试
while
循环。我也应该早些注意到这一点。