簡體   English   中英

while循環比for循環快1000倍以上嗎?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM