如何加速 Python 代碼?

本文講述了 5 個提高性能的方法,從使用更好的算法到多處理。

如何加速 Python 代碼?

1. 優化代碼和算法

一定要先好好看看你的代碼和算法。許多速度問題可以通過實現更好的算法或添加緩存來解決。本文所述都是關於這一主題的,但要遵循的一些一般指導方針是:

  • 測量,不要猜測。 測量代碼中哪些部分運行時間最長,先把重點放在那些部分上。
  • 實現緩存。 如果你從磁盤、網絡和數據庫執行多次重複的查找,這可能是一個很大的優化之處。
  • 重用對象,而不是在每次迭代中創建一個新對象。Python 必須清理你創建的每個對象才能釋放內存,這就是所謂的“垃圾回收”。許多未使用對象的垃圾回收會大大降低軟件速度。
  • 儘可能減少代碼中的迭代次數,特別是減少迭代中的操作次數。
  • 避免(深度)遞歸。 對於 Python 解釋器來說,它需要大量的內存和維護(Housekeeping)。改用生成器和迭代之類的工具。
  • 減少內存使用。 一般來說,儘量減少內存的使用。例如,對一個巨大的文件進行逐行解析,而不是先將其加載到內存中。
  • 不要這樣做。 聽起來很傻是吧?但是你真的需要執行這個操作嗎?不能晚點兒再執行嗎?或者可以只執行一次,並且它的結果可以存儲起來,而不是一遍又一遍地反覆計算?

2. 使用 PyPy

你可能正在使用 Python 的參考實現 CPython。之所以稱為 CPython,是因為它是用 C 語言編寫的。如果你確定你的代碼是 CPU 密集型(CPU bound)(如果你不知道這一術語,請參見本文“使用線程”一節)的話,那麼你應該研究一下 PyPy,它是 CPython 的替代方案。這可能是一種快速解決方案,無需更改任何一行代碼。

PyPy 聲稱,它的平均速度比 CPython 要快 4.4 倍。它是通過使用一種稱為 Just-in-time(JIT,即時編譯)技術來實現的。Java 和 .NET 框架就是 JIT 編譯的其他著名的例子。相比之下,CPython 使用解釋來執行代碼。雖然這一做法提供了很大的靈活性,但速度也變得慢了下來。

使用 JIT,你的代碼是在運行程序時即時編譯的。它結合了 Ahead-of-time(AOT,提前編譯)技術的速度優勢(由 C 和 C++ 等語言使用)和解釋的靈活性。另一個優點是 JIT 編譯器可以在運行時不斷優化代碼。代碼運行的時間越長,它就會變得越優化。

PyPy 在過去幾年中取得了長足的進步,通常情況下,它可以作為 Python 2 和 Python 3 的簡易替換方案。使用 Pipenv 這樣的工具,它也可以完美地工作,試試看吧!

3. 使用線程

大部分軟件都是 IO 密集型,而不是 CPU 密集型。如果你對這些術語還不熟悉的話,請看看下面的解釋:

  • IO 密集型(I/O bound):軟件主要是等待輸入 / 輸出操作完成才能工作。在從網絡或緩慢的存儲中獲取數據時,通常會出現這種情況。
  • CPU 密集型(CPU bound):軟件佔用了大量的 CPU 資源。它使用了 CPU 所有的能力來產生所需的結果。

在等待來自網絡或磁盤的應答時,你可以使用多個線程使其他部分保持運行狀態。

一個線程是一個獨立的執行序列。默認情況下,Python 程序有一個主線程。但你可以創建更多的主線程,並讓 Python 在它們之間切換。這種切換髮生得如此之快,以至於它們看上去就好像是在同時並排運行一樣。

如何加速 Python 代碼?

線程是獨立的執行序列,共享相同的內存空間

但與其他編程語言不同的是,Python 並不是同時運行的,而是輪流運行。這是因為 Python 中有一種全局解釋器鎖( Global Interpreter Lock,GIL)機制。這一點,以及 threading 庫在 我撰寫的關於 Python 併發性的文章 有詳細的解釋。

我們得到的結論是,線程對於 IO 密集型的軟件有很大的影響,但對 CPU 密集型的軟件毫無用處。

這是為什麼呢?很簡單。當一個線程在等待來自網絡的答覆時,其他線程可以繼續運行。如果你要執行大量的網絡請求,線程可以帶來巨大的差異。如果你的線程正在進行繁重的計算,那麼它們只是等待輪到它們繼續計算,線程化只會帶來更多的開銷。

4. 使用 Asyncio

Asyncio 是 Python 中一個相對較新的核心庫。它解決了與線程相同的問題:它加快了 IO 密集型軟件的速度,但這是以不同的方式實現的。我將立即坦承我並非 Python 的 asyncio 擁躉。它相當複雜,特別是對於初學者來說。我遇到的另一個問題是, asyncio 庫在過去幾年中有了很大的發展。網上的教程和示例代碼常常已經過時。不過,這並不意味著它就毫無用處。如果你有興趣的話,Real Python 網站有一個

不錯的 asyncio 指南

5 同時使用多個處理器

如果你的軟件是 CPU 密集型的,你通常可以用一種可以同時使用更多處理器的方式重寫你的代碼。通過這種方式,你就可以線性地調整執行速度。

這就是所謂的並行性,但並不是所有的算法都可以並行運行。例如,簡單的將遞歸算法進行並行化是不可能的。但是幾乎總有一種替代算法可以很好地並行工作。

使用更多處理處理器有兩種方式:

  1. 在同一臺機器內使用多個處理器和 / 或內核。在 Python 中,這可以通過 multiprocessing庫來完成。
  2. 使用計算機網絡來使用多個處理器,分佈在多臺計算機上。我們稱之為分佈式計算。

這篇 關於 Python 併發性的文章 側重於介紹如何在一臺機器的範圍內擴展 Python 軟件的方法。它還介紹了 multiprocessing 庫。如果你認為這是你需要的資料,一定要去看看。

與 threading 庫不同, multiprocessing 庫繞過了 Python 的全局解釋器鎖。它實際上是通過派生多個 Python 實例來實現這一點的。因此,現在你可以讓多個 Python 進程同時運行你的代碼,而不是在單個 Python 進程中輪流運行線程。

如何加速 Python 代碼?

多處理的可視化

multiprocessing 庫和 threading 庫非常相似。可能出現的問題是:為什麼還要考慮線程呢?答案是可以猜得到的。線程是“輕量”的:它需要更少的內存,因為它只需要一個正在運行的 Python 解釋器。產生新進程也還有其開銷。因此,如果你的代碼是 IO 密集型的,線程可能就足夠好了。

一旦你實現了軟件的並行工作,那麼在使用 Hadoop 之類的分佈式計算方面就前進了一小步。通過利用雲計算平臺,你可以相對輕鬆地進行擴展規模。例如,你可以在雲端中處理大型數據集,並在本地使用結果。使用混合操作的方式,你可以節省一些資金,因為雲端中的算力非常昂貴。

總結

總結起來就是:

  • 首先考慮優化你的算法和代碼。
  • 如果原始速度可以解決你的問題,請考慮使用 PyPy。
  • 對 IO 密集型軟件使用 threading
    庫和 asyncio 。
  • 使用 multiprocessing 庫解決 CPU 密集型問題。
  • 如果所有這些措施還不夠的話,可以利用 Hadoop 等雲計算平臺進行擴展規模。

Erik-Jan van Baaren,作家、軟件 / 數據工程師。

來源:https://www.tuicool.com/articles/3uuaiuN


分享到:


相關文章: