發(fā)布于:2021-01-25 13:49:30
0
831
0
Python是開(kāi)發(fā)人員中最受歡迎的編程語(yǔ)言之一,但是它有一定的局限性。例如,取決于應(yīng)用程序,它的速度可能是某些低級(jí)語(yǔ)言的速度的100倍。這就是為什么一旦Python的速度成為用戶(hù)的瓶頸,許多公司就會(huì)用另一種語(yǔ)言重寫(xiě)其應(yīng)用程序的原因。但是,如果有一種方法可以保留Python的出色功能并提高其速度呢?那就是PyPy。
PyPy是一個(gè)非常兼容的Python解釋器,是CPython 2.7、3.6和即將推出的3.7的一個(gè)有價(jià)值的替代品。通過(guò)安裝和運(yùn)行應(yīng)用程序,可以顯著提高速度。您將看到多少改進(jìn)取決于您正在運(yùn)行的應(yīng)用程序。
在本教程中,您將學(xué)習(xí):
如何使用PyPy安裝和運(yùn)行代碼
PyPy在速度方面如何與CPython進(jìn)行比較
PyPy的功能是什么以及它們?nèi)绾问鼓腜ython代碼運(yùn)行得更快
PyPy的局限性是什么
本教程中的示例使用Python 3.6,因?yàn)槟鞘荘yPy兼容的最新Python版本。
Python和PyPy
Python語(yǔ)言規(guī)范用于許多實(shí)現(xiàn)中,例如CPython(用C編寫(xiě)),Jython(用Java編寫(xiě)),IronPython(為.NET編寫(xiě))和PyPy(用Python編寫(xiě))。
CPython是Python的原始實(shí)現(xiàn),并且是迄今為止最受歡迎和最維護(hù)的。當(dāng)人們提到Python時(shí),他們通常不是指CPython。您現(xiàn)在可能正在使用CPython!
但是,由于CPython是高級(jí)解釋語(yǔ)言,因此CPython具有一定的局限性,不會(huì)為速度贏得任何榮譽(yù)。這就是PyPy可以派上用場(chǎng)的地方。由于它遵循Python語(yǔ)言規(guī)范,因此PyPy無(wú)需更改您的代碼庫(kù),并且由于您將在下面看到的功能而可以顯著提高速度。
現(xiàn)在,您可能想知道為什么CPython如果使用相同的語(yǔ)法,就無(wú)法實(shí)現(xiàn)PyPy的出色功能。原因是實(shí)現(xiàn)這些功能將需要對(duì)源代碼進(jìn)行巨大的更改,這將是一項(xiàng)艱巨的任務(wù)。
在不深入研究理論的情況下,讓我們看看PyPy的作用。
安裝
您的操作系統(tǒng)可能已經(jīng)提供了PyPy軟件包。例如,在macOS上,您可以借助Homebrew進(jìn)行安裝:
$ brew install pypy3
如果沒(méi)有,您可以下載適用于您的操作系統(tǒng)和體系結(jié)構(gòu)的預(yù)構(gòu)建二進(jìn)制文件。完成下載后,只需解壓縮tarball或ZIP文件即可。然后,您可以執(zhí)行PyPy,而無(wú)需將其安裝在任何地方:
$ 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.
在執(zhí)行上面的代碼之前,您需要位于下載二進(jìn)制文件的文件夾中。有關(guān)完整說(shuō)明,請(qǐng)參閱安裝文檔。
行動(dòng)中的PyPy
現(xiàn)在,您已經(jīng)安裝了PyPy,可以隨時(shí)使用它了!為此,創(chuàng)建一個(gè)名為的Python文件script.py,并將以下代碼放入其中:
total = 0 for i in range(1, 10000): for j in range(1, 10000): total += i + j print(f"The result is {total}")
這是一個(gè)腳本,在兩個(gè)嵌套for循環(huán)中,將的數(shù)字1加到9,999,然后輸出結(jié)果。
要查看運(yùn)行此腳本需要多長(zhǎng)時(shí)間,請(qǐng)對(duì)其進(jìn)行編輯以添加突出顯示的行:
該代碼現(xiàn)在執(zhí)行以下操作:
第3行將當(dāng)前時(shí)間保存到變量中start_time。
第5至8行運(yùn)行循環(huán)。
第10行打印結(jié)果。
第12行將當(dāng)前時(shí)間保存到end_time。
第13行顯示start_time和之間的差異,end_time以顯示運(yùn)行腳本所需的時(shí)間。
嘗試使用Python運(yùn)行它。這是我在2015 MacBook Pro上獲得的:
$ python3.6 script.py The result is 999800010000 It took 20.66 seconds to compute
現(xiàn)在使用PyPy運(yùn)行它:
$ pypy3 script.py The result is 999800010000 It took 0.22 seconds to compute
在這個(gè)小的綜合基準(zhǔn)中,PyPy的速度大約是Python的94倍!
有關(guān)更嚴(yán)格的基準(zhǔn)測(cè)試,您可以查看PyPy Speed Center,開(kāi)發(fā)人員在該中心每晚運(yùn)行帶有不同可執(zhí)行文件的基準(zhǔn)測(cè)試。
請(qǐng)記住,PyPy如何影響您的代碼性能取決于您的代碼在做什么。在某些情況下,PyPy實(shí)際上要慢一些,您將在后面看到。但是,就幾何平均而言,它的速度是Python的4.3倍。
PyPy及其功能
從歷史上看,PyPy提到了兩件事:
一個(gè)動(dòng)態(tài)語(yǔ)言框架,用于為動(dòng)態(tài)語(yǔ)言生成解釋器
一個(gè)Python實(shí)現(xiàn)使用框架
通過(guò)安裝PyPy并運(yùn)行一個(gè)小腳本,您已經(jīng)看到了第二個(gè)含義。您使用的Python實(shí)現(xiàn)是使用稱(chēng)為RPython的動(dòng)態(tài)語(yǔ)言框架編寫(xiě)的,就像CPython是用C編寫(xiě)而Jython是用Java編寫(xiě)一樣。
但是您是否沒(méi)有早些時(shí)候告訴過(guò)PyPy是用Python編寫(xiě)的?好吧,這有點(diǎn)簡(jiǎn)化。PyPy之所以被稱(chēng)為Python(而不是RPython)編寫(xiě)的解釋器,是因?yàn)镽Python使用與Python相同的語(yǔ)法。
為了清除所有內(nèi)容,以下是PyPy的生產(chǎn)方式:
源代碼是用RPython編寫(xiě)的。
所述RPython翻譯工具鏈被施加到代碼,這基本上使得代碼更高效。它還將代碼編譯為機(jī)器代碼,這就是Mac,Windows和Linux用戶(hù)必須下載不同版本的原因。
生成二進(jìn)制可執(zhí)行文件。這是您用來(lái)運(yùn)行小腳本的Python解釋器。
請(qǐng)記住,您無(wú)需完成所有這些步驟即可使用PyPy。該可執(zhí)行文件已經(jīng)可供您安裝和使用。
而且,由于在框架和實(shí)現(xiàn)中使用相同的詞非常令人困惑,因此PyPy背后的團(tuán)隊(duì)決定擺脫這種雙重用法。現(xiàn)在,PyPy僅指Python實(shí)現(xiàn)。該框架稱(chēng)為RPython轉(zhuǎn)換工具鏈。
接下來(lái),您將了解使PyPy在某些情況下比Python更好和更快的功能。
即時(shí)(JIT)編譯器
在了解什么是JIT編譯之前,讓我們退后一步,回顧一下諸如C之類(lèi)的已編譯語(yǔ)言和諸如JavaScript之類(lèi)的解釋語(yǔ)言的屬性。
編譯的編程語(yǔ)言性能更高,但是更難移植到不同的CPU體系結(jié)構(gòu)和操作系統(tǒng)。解釋性編程語(yǔ)言更可移植,但是其性能比編譯語(yǔ)言差很多。這是頻譜的兩個(gè)極端。
然后是諸如Python之類(lèi)的編程語(yǔ)言,它們同時(shí)進(jìn)行編譯和解釋。具體來(lái)說(shuō),首先將Python編譯為中間字節(jié)碼,然后由CPython對(duì)其進(jìn)行解釋。這使代碼的性能比用純解釋性編程語(yǔ)言編寫(xiě)的代碼更好,并且保持了可移植性的優(yōu)勢(shì)。
但是,性能仍然遠(yuǎn)不及編譯版本。原因是編譯后的代碼可以進(jìn)行很多優(yōu)化,而這些優(yōu)化是字節(jié)碼無(wú)法實(shí)現(xiàn)的。
這就是即時(shí)(JIT)編譯器的用處。它試圖通過(guò)對(duì)機(jī)器代碼進(jìn)行一些實(shí)際的編譯和一些解釋來(lái)獲得兩個(gè)方面的優(yōu)勢(shì)。簡(jiǎn)而言之,以下是JIT編譯為提高性能而采取的步驟:
標(biāo)識(shí)代碼中最常用的組件,例如循環(huán)中的函數(shù)。
在運(yùn)行時(shí)將這些零件轉(zhuǎn)換為機(jī)器代碼。
優(yōu)化生成的機(jī)器代碼。
與優(yōu)化的機(jī)器代碼版本交換以前的實(shí)現(xiàn)。
還記得本教程開(kāi)始時(shí)的兩個(gè)嵌套循環(huán)嗎?PyPy檢測(cè)到一次又一次地執(zhí)行相同的操作,將其編譯為機(jī)器代碼,優(yōu)化了機(jī)器代碼,然后交換了實(shí)現(xiàn)。這就是為什么您看到速度有了如此大的提高。
垃圾收集
每當(dāng)您創(chuàng)建變量,函數(shù)或任何其他對(duì)象時(shí),計(jì)算機(jī)就會(huì)為它們分配內(nèi)存。最終,將不再需要其中一些對(duì)象。如果不清理它們,則計(jì)算機(jī)可能會(huì)耗盡內(nèi)存并導(dǎo)致程序崩潰。
在C和C ++等編程語(yǔ)言中,通常必須手動(dòng)處理此問(wèn)題。其他編程語(yǔ)言(例如Python和Java)會(huì)自動(dòng)為您完成此操作。這稱(chēng)為自動(dòng)垃圾收集,有幾種技術(shù)可以實(shí)現(xiàn)它。
CPython使用一種稱(chēng)為引用計(jì)數(shù)的技術(shù)。本質(zhì)上,每當(dāng)引用對(duì)象時(shí),Python對(duì)象的引用計(jì)數(shù)就增加,而在取消引用對(duì)象時(shí),其計(jì)數(shù)就減少。當(dāng)引用計(jì)數(shù)為零時(shí),CPython會(huì)自動(dòng)為該對(duì)象調(diào)用內(nèi)存釋放函數(shù)。這是一種簡(jiǎn)單有效的技術(shù),但有一個(gè)陷阱。
當(dāng)大對(duì)象樹(shù)的引用計(jì)數(shù)變?yōu)榱銜r(shí),將釋放所有相關(guān)對(duì)象。結(jié)果,您可能有很長(zhǎng)的暫停時(shí)間,在此期間您的程序根本無(wú)法執(zhí)行。
另外,在一個(gè)用例中,引用計(jì)數(shù)根本不起作用??紤]以下代碼:
class A(object): pass a = A() a.some_property = a del a
在上面的代碼中,您定義了新類(lèi)。然后,創(chuàng)建該類(lèi)的實(shí)例,并將其分配為自身的屬性。最后,刪除實(shí)例。
此時(shí),該實(shí)例不再可訪(fǎng)問(wèn)。但是,引用計(jì)數(shù)不會(huì)從實(shí)例中刪除實(shí)例,因?yàn)樗哂袑?duì)自身的引用,因此引用計(jì)數(shù)不為零。此問(wèn)題稱(chēng)為參考周期,無(wú)法使用參考計(jì)數(shù)解決。
這是CPython使用另一種稱(chēng)為循環(huán)垃圾收集器的工具的地方。它從type對(duì)象的已知根開(kāi)始遍歷內(nèi)存中的所有對(duì)象。然后,它標(biāo)識(shí)所有可到達(dá)的對(duì)象并釋放不可達(dá)的對(duì)象,因?yàn)樗鼈儾辉俅嬖?。這解決了參考周期問(wèn)題。但是,當(dāng)內(nèi)存中有大量對(duì)象時(shí),它可能會(huì)導(dǎo)致更明顯的暫停。
另一方面,PyPy不使用引用計(jì)數(shù)。相反,它僅使用第二種技術(shù),即循環(huán)查找器。也就是說(shuō),它定期從根開(kāi)始遍歷活動(dòng)對(duì)象。PyPy相對(duì)于CPython具有一些優(yōu)勢(shì),因?yàn)樗粫?huì)打擾引用計(jì)數(shù),從而使內(nèi)存管理上花費(fèi)的總時(shí)間少于CPython。
另外,PyPy并沒(méi)有像CPython這樣的主要任務(wù)來(lái)完成所有工作,而是將工作分解為可變數(shù)量的片段,并逐個(gè)運(yùn)行直到?jīng)]有剩余為止。這種方法在每個(gè)次要集合之后僅增加幾毫秒,而不是像CPython那樣一次性增加數(shù)百毫秒。
垃圾收集很復(fù)雜,并且有許多詳細(xì)信息超出了本教程的范圍。您可以在文檔中找到有關(guān)PyPy垃圾回收的更多信息。
PyPy的局限性
PyPy不是靈丹妙藥,并且不一定總是最適合您的任務(wù)的工具。它甚至可能使您的應(yīng)用程序執(zhí)行速度比CPython慢得多。這就是為什么記住以下限制很重要。
C擴(kuò)展不能很好地工作
PyPy最適合純Python應(yīng)用程序。無(wú)論何時(shí)使用C擴(kuò)展模塊,它的運(yùn)行速度都比CPython中慢。原因是PyPy無(wú)法完全支持C擴(kuò)展模塊,因此無(wú)法對(duì)其進(jìn)行優(yōu)化。另外,PyPy必須模擬該部分代碼的引用計(jì)數(shù),從而使其速度更慢。
在這種情況下,PyPy團(tuán)隊(duì)建議刪除CPython擴(kuò)展并將其替換為純Python版本,以便JIT可以看到它并進(jìn)行優(yōu)化。如果這不是一個(gè)選擇,那么您將不得不使用CPython。
話(huà)雖如此,核心團(tuán)隊(duì)正在致力于C擴(kuò)展。一些軟件包已經(jīng)被移植到PyPy并以同樣快的速度工作。
它僅適用于長(zhǎng)期運(yùn)行的程序
假設(shè)您想去一家離您家很近的商店。您可以步行或開(kāi)車(chē)。
您的汽車(chē)顯然比腳快得多。但是,請(qǐng)考慮需要執(zhí)行以下操作:
去你的車(chē)庫(kù)。
啟動(dòng)你的車(chē)。
稍微加熱汽車(chē)。
開(kāi)車(chē)去商店。
查找停車(chē)位。
在返回途中重復(fù)該過(guò)程。
開(kāi)車(chē)要涉及很多開(kāi)銷(xiāo),如果您想去的地方在附近,這并不總是值得的!
現(xiàn)在,想想如果您想去五十英里外的鄰近城市會(huì)發(fā)生什么。開(kāi)車(chē)去那里而不是步行肯定是值得的。
盡管速度的差異并不像上面的類(lèi)比那么明顯,但是PyPy和CPython也是一樣。
當(dāng)您使用PyPy運(yùn)行腳本時(shí),它會(huì)做很多事情來(lái)使您的代碼運(yùn)行更快。如果腳本太小,那么開(kāi)銷(xiāo)將導(dǎo)致您的腳本運(yùn)行速度比CPython中慢。另一方面,如果您的腳本運(yùn)行時(shí)間很長(zhǎng),那么開(kāi)銷(xiāo)會(huì)帶來(lái)可觀的性能紅利。
要親自查看,請(qǐng)?jiān)贑Python和PyPy中運(yùn)行以下小腳本:
使用PyPy運(yùn)行時(shí),開(kāi)始時(shí)會(huì)有一個(gè)小的延遲,而CPython會(huì)立即運(yùn)行它。確切地說(shuō),0.0004873276在使用CPython的2015 MacBook Pro上運(yùn)行它需要幾秒鐘,而在0.0019447803PyPy上運(yùn)行它需要幾秒鐘。
它不進(jìn)行提前編譯
正如您在本教程開(kāi)始時(shí)所看到的那樣,PyPy不是完全編譯的Python實(shí)現(xiàn)。它編譯Python代碼,但不是Python代碼的編譯器。由于Python固有的動(dòng)態(tài)性,不可能將Python編譯成獨(dú)立的二進(jìn)制文件并重新使用它。
PyPy是一個(gè)運(yùn)行時(shí)解釋程序,它比完全解釋的語(yǔ)言要快,但是比完全編譯的語(yǔ)言(如C)要慢。
結(jié)論
PyPy是CPython的快速而強(qiáng)大的替代品。通過(guò)與腳本一起運(yùn)行腳本,無(wú)需對(duì)代碼進(jìn)行任何更改即可大大提高速度。但這不是靈丹妙藥。它有一些限制,您需要測(cè)試程序以查看PyPy是否可以提供幫助。
在本教程中,您學(xué)習(xí)了:
什么PyPy是
如何安裝PyPy并使用它運(yùn)行腳本
PyPy在速度方面如何與CPython進(jìn)行比較
PyPy具有什么功能以及如何提高程序速度
PyPy具有哪些限制,可能使其在某些情況下不適合
如果您的Python腳本需要提高速度,請(qǐng)嘗試PyPy。根據(jù)您的程序,您可能會(huì)得到一些明顯的速度改進(jìn)!
作者介紹
熱門(mén)博客推薦