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