PyPy:以最少的努力更快的 Python
目錄

Python 和 PyPy
安裝
PyPy 在行動
PyPy 及其特性
即時 (JIT) 編譯器
垃圾收集
PyPy 的局限性
它不適用于 C 擴展
它只適用于長時間運行的程序
它不進行提前編譯
結論
Python 是開發人員中最流行的編程語言之一,但它有一定的局限性。例如,根據應用程序的不同,它的速度可能是某些低級語言的100 倍。這就是為什么一旦 Python 的速度成為用戶的瓶頸,許多公司就會用另一種語言重寫他們的應用程序。但是,如果有一種方法可以保留 Python 的出色功能并提高其速度呢?輸入PyPy。
PyPy是一個非常兼容的 Python 解釋器,是 CPython 2.7、3.6 和即將推出的 3.7 的一個有價值的替代品。通過使用它安裝和運行您的應用程序,您可以獲得顯著的速度改進。您將看到多少改進取決于您正在運行的應用程序。
在本教程中,您將學習:
如何使用PyPy安裝和運行代碼
PyPy 與CPython在速度方面的比較
PyPy 的功能是什么以及它們如何使您的 Python 代碼運行得更快
PyPy 的局限性是什么
本教程中的示例使用 Python 3.6,因為這是 PyPy 兼容的最新 Python 版本。
Python 和 PyPy
Python語言規范用于許多實現,例如CPython(用C編寫)、Jython(用Java編寫)、IronPython(為 .NET 編寫)和 PyPy(用 Python 編寫)。
CPython 是 Python 的原始實現,是迄今為止最受歡迎和維護最多的。當人們提到 Python 時,他們通常指的是 CPython。您現在可能正在使用 CPython!
但是,由于它是一種高級解釋語言,因此 CPython 有一定的局限性,不會因速度而獲得任何獎牌。這就是 PyPy 可以派上用場的地方。由于它遵循 Python 語言規范,因此 PyPy 不需要更改您的代碼庫,并且由于您將在下面看到的功能,它可以提供顯著的速度改進。
現在,您可能想知道,如果 CPython 使用相同的語法,為什么不實現 PyPy 的出色功能。原因是實現這些功能需要對源代碼進行大量更改,并且將是一項重大任務。
在不深入研究理論的情況下,讓我們看看 PyPy 的實際應用。
安裝
您的操作系統可能已經提供了 PyPy 包。例如,在 macOS 上,您可以借助Homebrew安裝它:
$ brew install pypy3
如果沒有,您可以為您的操作系統和體系結構下載預構建的二進制文件。完成下載后,只需解壓 tarball 或 ZIP 文件即可。然后你就可以執行 PyPy 而不需要在任何地方安裝它:
$ tar xf pypy3.6-v7.3.1-osx64.tar.bz2 $ ./pypy3.6-v7.3.1-osx64/bin/pypy3 Python 3.6.9 (?, Jul 19 2020, 21:37:06) [PyPy 7.3.1 with GCC 4.2.1] Type "help", "copyright", "credits" or "license" for more information.
在執行上面的代碼之前,您需要位于下載二進制文件的文件夾中。有關完整說明,請參閱安裝文檔。
PyPy 在行動
你現在已經安裝了 PyPy,你可以看到它的運行了!為此,請創建一個名為的 Python 文件script.py并將以下代碼放入其中:
1total = 0 2for i in range(1, 10000): 3 for j in range(1, 10000): 4 total += i + j 5 6print(f"The result is {total}")
這是一個腳本,在兩個嵌套for循環中,將數字從1to相加9,999并打印結果。
要查看運行此腳本需要多長時間,請編輯它以添加突出顯示的行:
1import time 2 3start_time = time.time() 4 5total = 0 6for i in range(1, 10000): 7 for j in range(1, 10000): 8 total += i + j 9 10print(f"The result is {total}") 11 12end_time = time.time() 13print(f"It took {end_time-start_time:.2f} seconds to compute")
該代碼現在執行以下操作:
第 3行將當前時間保存到變量start_time。
第 5 到 8 行運行循環。
第 10 行打印結果。
第 12行將當前時間保存到end_time.
13個線打印之間的區別start_time,并end_time顯示它花了多長時間來運行的腳本。
嘗試用 Python 運行它。這是我在 2015 年 MacBook Pro 上得到的:
$ python3.6 script.py The result is 999800010000 It took 20.66 seconds to compute
現在用 PyPy 運行它:
$ pypy3 script.py The result is 999800010000 It took 0.22 seconds to compute
在這個小型綜合基準測試中,PyPy 的速度大約是 Python 的 94 倍!
對于更嚴格的基準測試,您可以查看 PyPy速度中心,其中開發人員使用不同的可執行文件每晚運行基準測試。
請記住,PyPy 如何影響您的代碼性能取決于您的代碼在做什么。在某些情況下,PyPy 實際上更慢,稍后您將看到。然而,在幾何平均上,它的速度是 Python 的 4.3 倍。
PyPy 及其特性
從歷史上看,PyPy 提到了兩件事:
一個動態語言的框架,用于產生口譯動態語言
一個Python實現使用框架
通過安裝 PyPy 并使用它運行一個小腳本,您已經看到了第二種含義。您使用的 Python 實現是使用名為RPython的動態語言框架編寫的,就像 CPython 是用 C 編寫的,而 Jython 是用 Java 編寫的。
但是你之前不是被告知 PyPy 是用 Python 編寫的嗎?嗯,這有點簡化。PyPy 被稱為用 Python(而不是用 RPython)編寫的 Python 解釋器的原因是 RPython 使用與 Python 相同的語法。
為了澄清一切,以下是 PyPy 的生成方式:
源代碼是用 RPython 編寫的。
所述RPython翻譯工具鏈被施加到代碼,這基本上使得代碼更高效。它還會將代碼編譯成機器碼,這就是 Mac、Windows 和 Linux 用戶必須下載不同版本的原因。
生成二進制可執行文件。這是您用來運行小腳本的 Python 解釋器。
請記住,您無需執行所有這些步驟即可使用 PyPy。可執行文件已經可供您安裝和使用。
此外,由于框架和實現使用同一個詞非常令人困惑,PyPy 背后的團隊決定擺脫這種雙重用法。現在,PyPy 僅指 Python 實現。該框架被稱為RPython 翻譯工具鏈。
接下來,您將了解在某些情況下使 PyPy 比 Python 更好更快的功能。
即時 (JIT) 編譯器
在深入了解什么是 JIT 編譯之前,讓我們先退后一步,回顧一下C 等編譯型語言和JavaScript等解釋型語言的特性。
編譯型編程語言的性能更高,但更難移植到不同的 CPU 架構和操作系統。解釋型編程語言具有更強的可移植性,但其性能比編譯型語言差很多。這是光譜的兩個極端。
然后是諸如 Python 之類的編程語言,它們混合了編譯和解釋。具體來說,Python首先被編譯成一個中間字節碼,然后由CPython解釋。這使得代碼比用純解釋性編程語言編寫的代碼性能更好,并且保持了可移植性優勢。
但是,性能仍然遠不及編譯版本。原因是編譯后的代碼可以進行很多字節碼無法實現的優化。
這就是即時 (JIT) 編譯器的用武之地。它試圖通過將一些真正的編譯成機器代碼和一些解釋來獲得兩個世界中更好的部分。簡而言之,以下是 JIT 編譯提供更快性能所需的步驟:
確定代碼中最常用的組件,例如循環中的函數。
在運行時將這些部分轉換為機器代碼。
優化生成的機器碼。
用優化的機器碼版本交換之前的實現。
還記得教程開頭的兩個嵌套循環嗎?PyPy 檢測到重復執行相同的操作,將其編譯為機器代碼,優化機器代碼,然后交換實現。這就是為什么你看到速度有如此大的提升。
垃圾收集
每當您創建變量、函數或任何其他對象時,您的計算機都會為它們分配內存。最終,將不再需要其中一些對象。如果您不清理它們,那么您的計算機可能會耗盡內存并使程序崩潰。
在 C 和 C++ 等編程語言中,您通常必須手動處理此問題。其他編程語言(例如 Python 和 Java)會自動為您完成。這稱為自動垃圾回收,有多種技術可以實現它。
CPython 使用一種稱為引用計數的技術。本質上,Python 對象的引用計數在對象被引用時遞增,在對象取消引用時遞減。當引用計數為零時,CPython 會自動調用該對象的內存釋放函數。這是一種簡單而有效的技術,但有一個問題。
當一棵大對象樹的引用計數變為零時,所有相關對象都被釋放。因此,您可能會暫停很長時間,在此期間您的程序根本沒有進展。
此外,還有一個用例,其中引用計數根本不起作用。考慮以下代碼:
1class A(object): 2 pass 3 4a = A() 5a.some_property = a 6del a
在上面的代碼中,您定義了新類。然后,您創建該類的一個實例并將其分配為自身的一個屬性。最后,刪除實例。
此時,該實例不再可訪問。但是,引用計數不會從內存中刪除該實例,因為它具有對自身的引用,因此引用計數不為零。這個問題叫做引用循環,不能用引用計數來解決。
這就是 CPython 使用另一個稱為循環垃圾收集器的工具的地方。它從像type對象這樣的已知根開始遍歷內存中的所有對象。然后它識別所有可達的對象并釋放不可達的對象,因為它們不再存在。這解決了參考循環問題。但是,當內存中有大量對象時,它會產生更明顯的暫停。
另一方面,PyPy 不使用引用計數。相反,它僅使用第二種技術,即循環查找器。也就是說,它周期性地從根開始遍歷活著的對象。這使 PyPy 比 CPython 有一些優勢,因為它不打擾引用計數,使得在內存管理上花費的總時間比在 CPython 中少。
此外,PyPy 不是在像 CPython 這樣的一項主要任務中完成所有工作,而是將工作分成可變數量的部分并運行每個部分,直到沒有剩余。這種方法在每個次要集合后只增加幾毫秒,而不是像 CPython 那樣一次性增加數百毫秒。
垃圾收集很復雜,并且有更多超出本教程范圍的詳細信息。您可以在文檔 中找到有關 PyPy 垃圾收集的更多信息。
PyPy 的局限性
PyPy 不是靈丹妙藥,可能并不總是最適合您的任務的工具。它甚至可能使您的應用程序執行得比 CPython 慢得多。這就是為什么記住以下限制很重要的原因。
它不適用于 C 擴展
PyPy 最適合純 Python 應用程序。每當您使用C 擴展模塊時,它的運行速度都比在 CPython 中慢得多。原因是 PyPy 不能優化 C 擴展模塊,因為它們沒有得到完全支持。此外,PyPy 必須模擬該部分代碼的引用計數,使其更慢。
在這種情況下,PyPy 團隊建議刪除 CPython 擴展并將其替換為純 Python 版本,以便 JIT 可以看到它并進行優化。如果這不是一個選項,那么您將不得不使用 CPython。
話雖如此,核心團隊正在研究 C 擴展。一些包已經被移植到 PyPy 并且工作得同樣快。
它只適用于長時間運行的程序
想象一下,你想去一家離你家很近的商店。你可以步行或開車。
你的車顯然比你的腳快得多。但是,請考慮一下它需要您做什么:
去你的車庫。
啟動你的車。
稍微暖一下車。
開車去商店。
找個停車位。
在回來的路上重復這個過程。
開車有很多開銷,如果你想去的地方就在附近,那并不總是值得的!
現在想想如果你想去五十英里外的鄰近城市會發生什么。開車去那里肯定是值得的,而不是步行。
盡管速度上的差異并不像上面的類比那樣明顯,但 PyPy 和 CPython 也是如此。
當您使用 PyPy 運行腳本時,它會做很多事情來使您的代碼運行得更快。如果腳本太小,那么開銷將導致您的腳本運行速度比在 CPython 中慢。另一方面,如果您有一個長時間運行的腳本,那么該開銷可以帶來顯著的性能紅利。
要親自查看,請在 CPython 和 PyPy 中運行以下小腳本:
1import time 2 3start_time = time.time() 4 5for i in range(100): 6 print(i) 7 8end_time = time.time() 9print(f"It took {end_time-start_time:.10f} seconds to compute")
當您使用 PyPy 運行它時,一開始會有一個小的延遲,而 CPython 會立即運行它。確切地說,0.0004873276在帶有 CPython 的 2015 MacBook Pro 上運行它需要幾秒鐘,而在0.0019447803PyPy上運行它需要幾秒鐘。
它不進行提前編譯
正如您在本教程開頭所見,PyPy 不是完全編譯的 Python 實現。它編譯Python 代碼,但它不是Python 代碼的編譯器。由于 Python 固有的動態性,將 Python 編譯成獨立的二進制文件并重用它是不可能的。
PyPy 是一種運行時解釋器,它比完全解釋的語言快,但比完全編譯的語言(如 C)慢。
結論
PyPy 是 CPython 的快速且功能強大的替代品。通過使用它運行您的腳本,您可以在不更改代碼的情況下獲得重大的速度提升。但這不是靈丹妙藥。它有一些限制,您需要測試您的程序以查看 PyPy 是否有幫助。
在本教程中,您學習了:
什么PyPy是
如何安裝PyPy 并用它運行你的腳本
PyPy 與CPython在速度方面的比較
PyPy 具有哪些功能以及它如何提高程序速度
PyPy 有哪些限制可能使其不適用于某些情況
如果您的 Python 腳本需要稍微提高速度,請嘗試使用 PyPy。根據您的程序,您可能會獲得一些明顯的速度提升!
Python 機器學習
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。