循环比循环快1000倍?

ZyT*_*van 1 python performance loops python-3.x

因此,之前已多次询问有关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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这使得for循环速度提高了3倍,但差异仍然是3个数量级.

为什么会这样?

Mar*_*ers 11

您正在创建10k range()对象.这需要一些时间来实现.然后,您必须为这些10k对象创建迭代器对象(以便for循环迭代值).接下来,for循环通过调用生成的迭代器上的__next__方法来使用迭代器协议.后两个步骤也适用于for列表上的循环.

但最重要的是,你在循环测试中作弊while.该while循环只运行一次,因为你永远不重置i0(感谢吉姆·希利亚德Fasarakis指出了这一点).你实际上在while19999次比较中运行一个循环; 第一次测试进行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
Run Code Online (Sandbox Code Playgroud)

看看这些数字有多接近?

我的机器要快一点,所以让我们创建一个基线进行比较; 这是在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; i+=1', number=10000)
5.172155902953818
Run Code Online (Sandbox Code Playgroud)

糟糕,所以正确运行在这里while实际上比较,那就是你的前提(和我的!).

我曾经pass避免回答关于引用该对象的速度有多快的问题(它很快但除此之外).我的时间比你的机器快6倍.

如果你想探索为什么迭代更快,你可以for在Python中定义循环的各个组件,从创建range()对象开始:

>>> timeit.timeit('range(10000)', number=10000)
0.0036197409499436617
Run Code Online (Sandbox Code Playgroud)

因此,创建10000个range()对象比运行while迭代10k次的单个循环花费更多时间.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
Run Code Online (Sandbox Code Playgroud)

相当快,但是; 它需要与单循环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
Run Code Online (Sandbox Code Playgroud)

男孩,10k次10k呼叫iter(range(1000)).__next__所需的时间比for循环管理的时间多近4倍; 这表明实际C实现的效率如何.

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

>>> (
...     timeit.timeit('9999 + 1', number=10000 ** 2) +
...     timeit.timeit('9999 < 10000', number=10000 ** 2)
... )    
3.695550534990616
Run Code Online (Sandbox Code Playgroud)

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


TLDR:您实际上没有while正确测试循环.我也应该早点注意到这一点.