[英]While loop >1000 times faster than for loop?
因此,關於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對象創建迭代器對象 ( for
循環迭代這些值)。 接下來, for
循環通過在生成的迭代器上調用__next__
方法來使用迭代器協議。 后兩個步驟也適用於列表的for
循環。
但最重要的是,您while
循環測試時作弊 。 while
循環只需要運行一次 ,因為您永遠不會將i
重置為0
(感謝Jim Fasarakis Hilliard指出了這一點 )。 實際上,您通過總計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倍。
如果您想探究為什么迭代會更快,可以從創建range()
對象開始計時Python中for
循環的各個組件:
>>> timeit.timeit('range(10000)', number=10000)
0.0036197409499436617
因此,與運行一個迭代10k次的while
循環相比,創建10000個range()
對象要花費更多的時間。 range()
對象的創建比整數創建的開銷更大。
這確實涉及全局名稱查找,這比較慢,您可以使用setup='_range = range'
然后使用_range(1000)
使其更快。 大約刮掉了三分之一的時間。
接下來,為此創建一個迭代器; 在這里,我將為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
Boy,10k乘以10k調用iter(range(1000)).__next__
花的時間幾乎是for
循環管理的4倍; 這表明了實際C實現的效率。
但是,它的確說明了C代碼中的循環要快得多,這就是為什么正確執行while
循環實際上會更慢的原因。 相加整數並在字節碼中進行布爾比較比在C代碼中對range()
進行迭代(CPU直接在CPU寄存器中進行增量和比較range()
花費的時間更多:
>>> (
... timeit.timeit('9999 + 1', number=10000 ** 2) +
... timeit.timeit('9999 < 10000', number=10000 ** 2)
... )
3.695550534990616
正是這些操作使while
循環慢了大約3秒。
TLDR:您實際上沒有正確測試while
循環。 我也應該早些注意到這一點。
您計時不正確, setup
僅執行一次 ,然后所有后續運行的i
值為10000
。 請參閱timeit
文檔:
主語句的時間編號執行。 這將執行一次
setup
語句,然后返回執行主語句所需的時間,以秒為單位,以浮點數為單位。
另外,通過為每個重復打印i
來進行驗證:
>>> timeit('print(i)\nwhile i<10000: True; i+=1',setup='i=0', number=5)
0
10000
10000
10000
10000
結果,所有后續運行僅執行比較(這是True
)並盡早完成。
正確計時,並查看for
循環實際上如何更快:
>>> timeit('i=0\nwhile i<10000: True; i+=1', number=10000)
8.416439056396484
>>> timeit('for i in range(10000): True', number=10000)
5.589155912399292
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.