早上我偶然看見一篇介紹兩個Python腳本的博文,其中一個效率更高。這篇博文已經被刪除,所以我沒辦法給出文章鏈接,但腳本基本可以歸結如下:
fast.py
import timea = [i for i in range(1000000)]sum = 0t1 = time.time()for i in a: sum = sum + it2 = time.time()print t2-t1
slow.py
import timefrom random import shufflea = [i for i in range(1000000)]shuffle(a)sum = 0t1 = time.time()for i in a: sum = sum + it2 = time.time()print t2-t1
如你所見,兩個腳本有完全相同的行為。都產生一個包含前一百萬個整數的列表,并打印對這些整數求和的時間。唯一的不同是 slow.py 先將整數隨機排序。盡管這看起來有些奇怪,似乎隨機化足夠將程序明顯變慢。在我機器上,運行的Python2.7.3, fast.py 始終比 slow.py 快十分之一秒(fast.py 執行大約耗時四分之三秒,這是不平常的增速)。你不妨也試試看。(我沒有在Python3上測試,但結果應該不會差太多。)
那為什么列表元素隨機化會導致這么明顯的減速呢?博文的原作者把這記作“分支預測(branch prediction)”。如果你對這個術語不熟悉,可以在 StackOverflow 的提問中看看,這里很好地解釋了這個概念。(我的疑慮是原文的原作者遇到了這個問題或者與此類似的問題,并把這個想法應用到不太適合應用的Python片段中。)
當然,我懷疑分支預測(branch prediction)是否是真正導致問題的原因。在這份Python代碼中沒有頂層條件分支,而且合乎情理的是兩個腳本在循環體內有嚴格一致的分支。程序中沒有哪一部分是以這些整數為條件的,并且每個列表的元素都是不依賴于數據本身的。當然,我還是不確定python是否算得上足夠“底層”,以至于CPU級別的分支預測能夠成為python腳本性能分析中的一個因素。Python畢竟是一門高級語言。
因此,如果不是分支預測的原因,那為什么 slow.py 會這么慢?通過一點研究,經過一些“失敗的開端”之后,我覺得自己找到了問題。這個答案需要對Python內部虛擬機有點熟悉。
失敗的開端:列表vs.生成器(lists and generators)
我的第一想法是Python對排序的列表[i for i in range(1000000)] 的處理效率要比隨機列表高。換句話說,這個列表可以用下面的生成器替代:
def numbers(): i = 0 while i < 1000000: yield i i += 1
我想這可能在時間效率上更高效些。畢竟,如果Python在內部使用生成器替代真正的列表可以避免在內存中一次保存所有整數的麻煩,這可以節省很多開銷。slow.py 中的隨機列表不能輕易的被一個簡單生成器捕獲,所有VM(虛擬機)無法進行這樣的優化。
新聞熱點
疑難解答