每日最新頭條.有趣資訊

如何使 Python 程序快如閃電,提速 30%?

作者丨Martin Heinz

譯者 | 平川

策劃 | 劉燕

討厭 Python 的人總是說,他們不想使用它的原因之一是它很慢。不管使用什麽編程語言,程序是快還是慢都在很大程度上取決於編寫程序的開發人員,以及他們編寫最優化快速程序的技能和能力。在本文中,讓我們來證明一下某些人的“誤解”,看看如何提高 Python 程序的性能,使它們變得非常快!

本文最初發布於 martinheinz.dev 部落格,經原作者授權由 InfoQ 中文站翻譯並分享。

計時和性能分析

在我們開始優化任何東西之前,我們首先需要找出到底是代碼的哪些部分減慢了整個程序。有時候,程序的瓶頸可能是顯而易見的,但如果你不知道它在哪裡,那麽以下選項可以幫你找出來。

這是我將用於演示的程序,它計算 e 的 X 次方(摘自 Python 文檔):

最懶的“性能分析”

首先是最簡單同時又非常懶惰的解決方案——Unix time 命令:

如果你只是想計算整個程序的運行時間,這就行了,但這通常不能滿足需求……

最詳細的性能分析

另一個極端是 cProfile,它提供的信息又太多了:

在這裡,我們使用 cProfile 模塊和 time 參數運行測試腳本,這樣就可以根據內部時間(cumtime)對代碼行進行排序。這給了我們很多信息,上面的內容大約是實際輸出的 10%。從這裡,我們可以看到 exp 函數是罪魁禍首(驚喜!),現在我們可以得到更具體的時間和性能分析…

對具體的函數計時

現在我們知道了應該將注意力放在哪裡,我們可能希望對慢速函數進行計時,而不需要測量代碼的其余部分。我們可以使用簡單的裝飾器:

接下來,可以把這個裝飾器應用到函數上,像下面這樣:

輸出如下:

需要考慮的一件事是我們實際上(想)測量的是哪種時間。時間包提供了 time.perf_counter 和 time.process_time。它們的不同之處在於 perf_counter 返回絕對值,其中包括 Python 程序進程不運行時的時間,因此可能會受到機器負載的影響。另一方面,process_time 隻返回用戶時間(不包括系統時間),只是進程的時間。

使之變快

有趣的部分來了。我們將讓你的 Python 程序運行得更快一些。我(基本上)不會向你展示一些能夠神奇地解決性能問題的駭客技術、技巧和代碼片段。這裡介紹的更多的是一般的想法和策略,當你使用它們時,可以對性能產生巨大的影響,在某些情況下可以提高 30% 的速度。

使用內置數據類型

這一點很明顯。內置數據類型非常快,特別是與樹或鏈表等自定義類型相比。這主要是因為內置類型是用 C 實現的,在用 Python 編碼時,我們無法在速度上與之匹配。

使用 lru_cache 緩存數據

我已經在之前的博文中介紹過這個,但是我認為值得通過一個簡單的例子再說明一下:

上面的函數使用 time.sleep 模擬大量計算。第一次使用參數 1 調用時,它將等待 2 秒,然後才返回結果。當再次調用時,結果已經被緩存,因此,它會跳過函數體並立即返回結果。

使用局部變量

這與在每個作用域內查找變量的速度有關。我會寫每個作用域,因為它不只關乎使用局部變量還是全局變量。查找速度也確實存在差異,函數中的局部變量最快,類級屬性(例如 self.name)次之,而全局(例如導入的函數 time.time)變量最慢。

你可以像下面這樣,使用不必要的賦值來提升性能:

使用函數

這看起來可能不符合直覺,因為調用函數會將更多的東西放到堆棧中,從函數返回時會產生開銷,但這與前面一點有關。如果你只是將整個代碼放入一個文件中,而不將其放入函數中,那麽由於全局變量的關係,速度會慢很多。因此,你只是將整個代碼封裝在 main 函數中並調用一次,就可以加快你的代碼,像這樣:

不要訪問屬性

另一個可能降低程序速度的是點操作符(.),它可以用於訪問對象屬性。這個操作符使用 _getattribute__ 觸發字典查找,這會在代碼中產生額外的開銷。那麽,我們如何才能避免(限制)使用它呢?

提防字元串

在循環中運行諸如模數(%s)或.format() 之類的方法時,對字元串的操作可能會變得非常慢。我們還有什麽更好的選擇嗎?根據 Raymond Hettinger 最近的推文,我們唯一應該使用的是 f-string,它是最易讀、最簡潔、最快速的方法。因此,根據那條推文,你可以使用以下方法——從最快的到最慢的:

生成器本身並沒有更快,因為它們允許延遲計算,這節省的是記憶體而不是時間。但是,節省的記憶體可能會使得程序在實際運行時更快。為什麽?如果你有一個大型數據集,並且沒有使用生成器(迭代器),那麽數據可能會溢出 CPU L1 緩存,這將顯著降低在記憶體中查找值的速度。

說到性能,很重要的一點是 CPU 可以將它正在處理的所有數據保存在緩存中。你可以看下 Raymond Hettingers 的演講,他提到了這些問題。

小結

優化的第一原則是不做優化。但是,如果你真的需要,我希望這些小技巧能幫到你。不過,在優化代碼時要注意,因為它可能會使代碼難於閱讀、難於維護,甚至超過優化帶來的好處。

https://martinheinz.dev/blog/13

點個在看少個 bug

獲得更多的PTT最新消息
按讚加入粉絲團