因此,之前已多次询问有关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个数量级。
为什么会这样?
答案 0 :(得分:11)
您正在创建10k range()
个对象。这需要一些时间来实现。然后,您必须为这些10k对象创建iterator objects(对于for
循环来迭代值)。接下来,for
循环通过调用生成的迭代器上的__next__
method来使用迭代器协议。后两个步骤也适用于列表上的for
循环。
但最重要的是,你在while
循环测试中作弊。 while
循环只需运行一次,因为您永远不会将i
重置回0
(感谢Jim Fasarakis Hilliard pointing that out)。您实际上在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; i+=1', number=10000)
5.172155902953818
哎呀,正确运行while
实际上慢,这里有你的前提(和我的!)。
我使用pass
来避免回答有关引用该对象的速度有多快的问题(除此之外它很快)。我的时间比你的机器快6倍。
如果你想探索为什么迭代更快,你可以在Python中定义for
循环的各个组件,从创建range()
对象开始:
>>> timeit.timeit('range(10000)', number=10000)
0.0036197409499436617
因此,创建10000 range()
个对象比运行迭代10k次的单个while
循环花费更多时间。 range()
对象的创建成本比整数更高。
这确实涉及全局名称查找,速度较慢,您可以使用setup='_range = range'
然后使用_range(1000)
来加快速度。这大约是时间的1/3。
接下来,为此创建一个迭代器;在这里,我将使用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
男孩,10k次拨打iter(range(1000)).__next__
的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
循环。我也应该早点注意到这一点。
答案 1 :(得分:2)
您错误地计时,setup
只执行一次,然后{em}的值为i
所有后续运行。请参阅timeit
上的文档:
主要陈述的时间编号执行。这将执行一次
10000
语句,然后返回执行主语句多次所需的时间,以秒为单位测量为浮点数。
另外,每次重复打印setup
进行验证:
i
因此,所有后续运行仅执行比较(>>> timeit('print(i)\nwhile i<10000: True; i+=1',setup='i=0', number=5)
0
10000
10000
10000
10000
)并提前完成。
正确的时间,看看True
循环实际上是如何更快的:
for