如何用PyPy讓你的Python代碼運行得更快
Python是開發(fā)人員中最常用的編程語言之一,但它有一定的局限性。例如,對于某些應(yīng)用程序而言,它的運行速度可能比其它語言低100倍。這就是為什么當(dāng)Python的運行速度成為用戶瓶頸后,許多公司會用另一種語言重寫他們的應(yīng)用程序。但是有沒有一種方法既可以保持Python的特性又能提高速度呢?它就是PyPy。
PyPy是一種非常兼容的Python解釋器,它是CPython2.7、3.6和即將推出的3.7的一種值得替代的方法。在安裝和運行應(yīng)用程序時使用它,可以顯著提高速度。速度提高多少取決于你運行的應(yīng)用程序。
在本教程中,您將學(xué)習(xí):
如何使用PyPy安裝和運行代碼 PyPy與CPython在速度方面的比較 PyPy的功能及其如何使Python代碼更快地運行 本教程中的示例使用 Python 3.6 ,因為它是PyPy兼容的最新 Python 版本。PyPy 簡介
Python解釋器可以用多種語言來實現(xiàn),如CPython(用C編寫)、Jython(用Java編寫)、Iron Python(用.NET編寫)和PyPy(用Python編寫)。
CPython是Python解釋器的最初實現(xiàn),也是迄今為止使用最廣和最多維護的。當(dāng)我們從Python官方網(wǎng)站下載并安裝好Python 3.x后,我們就直接獲得了一個官方版本的解釋器:CPython。這個解釋器是用C語言開發(fā)的,所以叫CPython。在命令行下運行python就是啟動CPython解釋器。
但是,由于CPython是一種高級的解釋語言,因此它有一定的局限性,并且在速度方面沒有任何優(yōu)勢。這就是PyPy可以起作用的地方。由于它符合Python語言規(guī)范,因此Py Py不需要對代碼庫進行任何更改,并且可以通過下面的功能顯著提高速度。
現(xiàn)在,您可能想知道,如果CPython使用相同的語法,為什么它不實現(xiàn)Py Py的強大功能。原因是,實施這些功能需要對源代碼進行巨大的更改,這將是一項非常繁瑣的工作。
我們來粗略看一下如何在實際操作中使用PyPy。
安裝
您的操作系統(tǒng)可能已提供PyPy軟件包。例如,在Mac OS上,您可以在Homebrew的幫助下安裝它:
$ brew install pypy3
或者您也可以下載與操作系統(tǒng)匹配的二進制文件。完成下載后,只需打開tarball或ZIP文件即可。然后,您可以執(zhí)行以下操作:
$ tar xf pypy3.6-v7.3.1-osx64.tar.bz2$ ./pypy3.6-v7.3.1-osx64/bin/pypy3Python 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.
您需要在上述文件夾地址執(zhí)行該命令。有關(guān)完整的說明,請參閱安裝文檔。
運行 PyPy
您現(xiàn)在已經(jīng)安裝了Py Py,并且即將運行它!為此,請創(chuàng)建一個名為script.py的Python文件,并將以下代碼放入其中:
total = 0for i in range(1, 10000): for j in range(1, 10000): total += i + j print(f'The result is {total}')
在兩個嵌套的for循環(huán)中,將1到9,999之間的數(shù)字相加,并打印結(jié)果。
查看運行此腳本需要多長時間:
import time start_time = time.time() total = 0for i in range(1, 10000): for j in range(1, 10000): total += i + j print(f'The result is {total}') end_time = time.time()print(f'It took {end_time-start_time:.2f} seconds to compute')
該代碼現(xiàn)在執(zhí)行以下操作:
第3行將當(dāng)前時間保存到變量start_time。 第5至8行運行循環(huán)。 第10行打印結(jié)果。 第12行將當(dāng)前時間保存為end_time。 第13行打印開始時間和結(jié)束時間之間的差值,以顯示運行腳本所需的時間。用Python來運行它。下面是我在Mac Book Pro上的結(jié)果:
$ python3.6 script.pyThe result is 999800010000It took 20.66 seconds to compute
現(xiàn)在使用Py Py運行它:
$ pypy3 script.pyThe result is 999800010000It took 0.22 seconds to compute
在這個小實驗中,PyPy的速度大約是Python的94倍!
您可以通過瀏覽 PyPy Speed Center 來查看更多嚴(yán)格的測試。
請記住,PyPy如何影響代碼的性能取決于您用代碼來做什么。在某些情況下,Py Py實際上較慢,稍后會看到。但是,就幾何平均而言,它的速度是Python的4.3倍。
PyPy及其特性
Py Py有兩種定義:
1、用于生成動態(tài)語言解釋器的動態(tài)語言框架 2、使用該框架的Python實現(xiàn)
您應(yīng)該已經(jīng)意識到了第二個問題。您使用的Python實現(xiàn)是使用稱為RPython的動態(tài)語言框架編寫的,就像CPython是用C編寫的,而Jython是用Java編寫的一樣。
但之前文中不是提到PyPy是用Python編寫的嗎?嗯,這有點簡單。PyPy成為用Python編寫的Python解釋器(而不是RPython)這么說的原因是RPython使用了與Python相同的語法。
PyPy是怎么來的?需要解釋以下幾點:
1、它的源代碼是用RPython編寫。
2、RPython轉(zhuǎn)換工具應(yīng)用到了代碼中,從根本上提高了代碼效率,還可以將代碼編譯為機器代碼,這就是Mac,Windows和Linux用戶必須下載不同版本的原因。
3、用上述方式生成的二進制可執(zhí)行文件,就是你運行的Python解釋器。
你不需要執(zhí)行上述所有這些步驟來使用PyPy。因為已經(jīng)有提供您安裝和使用的可執(zhí)行文件。
此外,由于在框架和實現(xiàn)中使用同一個詞非常令人困惑,PyPy背后的團隊決定放棄這種雙重用法。現(xiàn)在,PyPy僅指Python解釋器,而框架被稱為RPython轉(zhuǎn)換工具。
接下來,您將了解在什么情況下使用PyPy比Python更好、更快。
Just-In-Time (JIT) 編譯器
在了解JIT編譯器的內(nèi)容之前,讓我們先回顧一下已編譯語言(如C)和解釋語言(如JavaScript)的特性。
在編譯型語言寫的程序執(zhí)行之前,需要一個專門的編譯過程,把源代碼編譯成機器語言的文件,如exe格式的文件,以后要再運行時,直接使用編譯結(jié)果即可,如直接運行exe文件。因為只需編譯一次,以后運行時不需要編譯,所以編譯型語言執(zhí)行效率高。與特定平臺相關(guān),一般無法移植到其他平臺。如C、C++、Objective等都屬于編譯型語言。
解釋型語言不需要事先編譯,其直接將源代碼解釋成機器碼并立即執(zhí)行,所以只要某一平臺提供了相應(yīng)的解釋器即可運行該程序。解釋型語言每次運行都需要將源代碼解釋稱機器碼并執(zhí)行,效率較低;只要平臺提供相應(yīng)的解釋器,就可以運行源代碼,所以可以方便源程序移植。
然后還有一些編程語言,例如Python,它混合了編譯和解釋。具體來說,Python首先編譯為字節(jié)碼,然后由CPython解釋。這使代碼的性能優(yōu)于用純解釋型語言編寫的代碼,并保持可移植性優(yōu)勢。
但是它的性能仍然遠遠低于編譯型語言。其原因是,編譯后的代碼可以執(zhí)行許多優(yōu)化,而字節(jié)碼是不可能的。
這就是JIT編譯器的來源。它試圖通過對機器代碼進行一些編譯和一些解釋來同時獲得兩種優(yōu)勢。簡而言之,以下是JIT編譯為提供更快性能所采取的步驟:
1、識別代碼中最常用的組件,如循環(huán)中的函數(shù)。
2、運行時將這些部件轉(zhuǎn)換為機器代碼。
3、優(yōu)化生成的機器代碼。
4、用優(yōu)化的機器代碼版本取代之前的實現(xiàn)。
還記得教程開頭的兩個嵌套循環(huán)嗎?PyPy檢測到重復(fù)執(zhí)行相同操作時,將其編譯為機器代碼,優(yōu)化機器代碼,然后轉(zhuǎn)換實現(xiàn)。這也是為什么您會看到這樣的結(jié)果。
垃圾回收機制
無論何時創(chuàng)建變量、函數(shù)或任何其他對象,您的計算機都會給它們分配內(nèi)存。最終,其中一些對象將不再需要。如果不及時清理,計算機可能會耗盡內(nèi)存并使程序崩潰。
在C和C++等編程語言中,通常必須手動處理此問題。其他編程語言(如Python和Java)會自動為您執(zhí)行此操作。這被稱為自動垃圾回收機制。
CPython使用一種稱為引用計數(shù)的技術(shù)。實質(zhì)上,每當(dāng)引用對象時,Python對象的引用計數(shù)都會增加,而在取消引用該對象時則遞減計數(shù)。當(dāng)引用計數(shù)為零時,CPython會自動為該對象調(diào)用內(nèi)存釋放函數(shù)。這是一種簡單有效的技術(shù),但有一個陷阱。
當(dāng)大型對象樹的引用計數(shù)變?yōu)榱銜r,所有相關(guān)對象將被釋放。因此,您可能有很長的暫停時間,在此期間您的程序根本無法執(zhí)行。
此外,還有一個例子,其中引用計數(shù)根本不起作用。如下所示:
class A(object): pass a = A()a.some_property = adel a
在上面的代碼中,定義了新的類,然后,創(chuàng)建一個實例,并將其指定為其自身的屬性。最后,刪除實例。
此時,實例將不再可訪問。但是,引用計數(shù)不會從內(nèi)存中刪除實例,因為它具有對自身的引用,因此引用計數(shù)不是零。此問題被稱為引用循環(huán),無法使用引用計數(shù)解決。
這是CPython使用的另一個工具,稱為循環(huán)垃圾回收器。它從已知根(如類型對象)開始遍歷內(nèi)存中的所有對象。然后,它標(biāo)識所有可訪問的對象,并釋放不可訪問的對象,因為它們不再存在。這樣就解決了引用循環(huán)問題。但是,當(dāng)內(nèi)存中存在大量對象時,它可能會創(chuàng)建更明顯的暫停。
另一方面,PyPy不使用引用計數(shù)。相反,它只使用第二種技術(shù),即循環(huán)查找器。也就是說,它會定期從根開始遍歷活動對象。這使PyPy比CPython具有一些優(yōu)勢,因為它不需要考慮引用計數(shù),從而使內(nèi)存管理花費的總時間少于CPython。
此外,PyPy將工作拆分為可變數(shù)量的部分,并運行每個部分,直到?jīng)]有剩余部分為止。此方法只在每個次要集合之后添加幾毫秒,而不像CPython那樣一次添加數(shù)百毫秒。
垃圾回收機制非常復(fù)雜,并且有許多超出本教程范圍的內(nèi)容。您可以在文檔中找到有關(guān)PyPy垃圾回收機制的詳細(xì)信息。
PyPy的局限性
PyPy并非萬能,它不是一個適合您所有任務(wù)的工具。它甚至可能使應(yīng)用程序的執(zhí)行速度比CPython慢得多。這就是為什么您必須記住以下局限性。
它不適用于C擴展
PyPy最適合純Python應(yīng)用程序。無論何時使用C擴展模塊,它的運行速度都要比在CPython中慢得多。原因是PyPy無法優(yōu)化C擴展模塊,因為它們不受完全支持。此外,PyPy必須模擬代碼中的引用計數(shù),使其更慢。
在這種情況下,PyPy團隊建議去掉CPython擴展并將其替換為純Python版本。如果不行的話,則必須使用CPython。
盡管如此,核心團隊正在處理C擴展。有些軟件包已被移植到PyPy,并且工作速度也同樣快。
它只適用于長時間運行的程序
想象一下你想去一家離你家很近的商店。您既可以直接走路前往,也可以開車。
您的車明顯比您的腳快得多。但是,請考慮需要您完成的步驟:
1.去你的車庫。
2、開車。
3、給車預(yù)熱。
4、開車去商店。
5、尋找停車位。
6、在返回途中重復(fù)此過程。
開車需要一系列麻煩的步驟,如果你想去的地方就在附近,那就不一定值得了。
現(xiàn)在想想,如果你想去50公里外的鄰近城市,會發(fā)生什么?開車去那里肯定是值得的,而不是步行去。
雖然速度上的對比并不像上面的類比那樣明顯,但PyPy和CPython和這個道理一樣。
當(dāng)使用PyPy運行腳本時,它會執(zhí)行許多操作以使代碼運行得更快。如果腳本本身很簡單,則實際腳本運行速度會低于CPython。另一方面,如果您有一個長時間運行的腳本,那么可能會帶來顯著的性能提升。
想親自感受一下的話,請在CPython和PyPy中運行以下小腳本:
import time start_time = time.time() for i in range(100): print(i) end_time = time.time()print(f'It took {end_time-start_time:.10f} seconds to compute')
當(dāng)您使用PyPy運行它時,開始時會有一個小延遲,而CPython會立即運行它。在Mac Book Pro上運行它,用CPython需要0.0004873276秒,用PyPy需要0.0019447803秒。
它不執(zhí)行提前編譯
正如您在本教程開頭所看到的,PyPy不是一個完全編譯型的Python實現(xiàn)。它編譯Python代碼,但不是Python代碼的編譯器。由于Python固有的一些特性,導(dǎo)致無法將Python編譯為獨立的二進制文件并重用它。
Py Py比完全解釋型的語言快,但比完全編譯的語言(如C)慢。
總結(jié)
PyPy是CPython的一種快速且功能強大的替代方案。使用它運行腳本,您可以在不更改代碼的情況下大大提高速度。但它也不是萬能的,有一些局限性。
到此這篇關(guān)于如何用PyPy讓你的Python代碼運行得更快的文章就介紹到這了,更多相關(guān)Python PyPy 內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
