1. 程式人生 > 程式設計 >如何用PyPy讓你的Python程式碼執行得更快

如何用PyPy讓你的Python程式碼執行得更快

Python是開發人員中最常用的程式語言之一,但它有一定的侷限性。例如,對於某些應用程式而言,它的執行速度可能比其它語言低100倍。這就是為什麼當Python的執行速度成為使用者瓶頸後,許多公司會用另一種語言重寫他們的應用程式。但是有沒有一種方法既可以保持Python的特性又能提高速度呢?它就是PyPy。

PyPy是一種非常相容的Python直譯器,它是CPython2.7、3.6和即將推出的3.7的一種值得替代的方法。在安裝和執行應用程式時使用它,可以顯著提高速度。速度提高多少取決於你執行的應用程式。

在本教程中,您將學習:

  1. 如何使用PyPy安裝和執行程式碼
  2. PyPy與CPython在速度方面的比較
  3. PyPy的功能及其如何使Python程式碼更快地執行
  4. 本教程中的示例使用 Python 3.6 ,因為它是PyPy相容的最新 Python 版本。

PyPy 簡介

Python直譯器可以用多種語言來實現,如CPython(用C編寫)、Jython(用Java編寫)、Iron Python(用.NET編寫)和PyPy(用Python編寫)。

CPython是Python直譯器的最初實現,也是迄今為止使用最廣和最多維護的。當我們從Python官方網站下載並安裝好Python 3.x後,我們就直接獲得了一個官方版本的直譯器:CPython。這個直譯器是用C語言開發的,所以叫CPython。在命令列下執行python就是啟動CPython直譯器。

但是,由於CPython是一種高階的解釋語言,因此它有一定的侷限性,並且在速度方面沒有任何優勢。這就是PyPy可以起作用的地方。由於它符合Python語言規範,因此Py Py不需要對程式碼庫進行任何更改,並且可以通過下面的功能顯著提高速度。

現在,您可能想知道,如果CPython使用相同的語法,為什麼它不實現Py Py的強大功能。原因是,實施這些功能需要對原始碼進行巨大的更改,這將是一項非常繁瑣的工作。

我們來粗略看一下如何在實際操作中使用PyPy。

安裝

您的作業系統可能已提供PyPy軟體包。例如,在Mac OS上,您可以在Homebrew的幫助下安裝它:

$ brew install pypy3

或者您也可以下載與作業系統匹配的二進位制檔案。完成下載後,只需開啟tarball或ZIP檔案即可。然後,您可以執行以下操作:

$ 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

您現在已經安裝了Py Py,並且即將執行它!為此,請建立一個名為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}")

在兩個巢狀的for迴圈中,將1到9,999之間的數字相加,並列印結果。

檢視執行此指令碼需要多長時間:

import time
 
start_time = time.time()
 
total = 0
for i 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")

該程式碼現在執行以下操作:

  • 第3行將當前時間儲存到變數start_time。
  • 第5至8行執行迴圈。
  • 第10行列印結果。
  • 第12行將當前時間儲存為end_time。
  • 第13行列印開始時間和結束時間之間的差值,以顯示執行指令碼所需的時間。

用Python來執行它。下面是我在Mac Book Pro上的結果:

$ python3.6 script.py
The result is 999800010000
It took 20.66 seconds to compute

現在使用Py Py執行它:

$ pypy3 script.py
The result is 999800010000
It took 0.22 seconds to compute

在這個小實驗中,PyPy的速度大約是Python的94倍!

您可以通過瀏覽 PyPy Speed Center 來檢視更多嚴格的測試。

請記住,PyPy如何影響程式碼的效能取決於您用程式碼來做什麼。在某些情況下,Py Py實際上較慢,稍後會看到。但是,就幾何平均而言,它的速度是Python的4.3倍。

PyPy及其特性

Py Py有兩種定義:

1、用於生成動態語言直譯器的動態語言框架 2、使用該框架的Python實現

您應該已經意識到了第二個問題。您使用的Python實現是使用稱為RPython的動態語言框架編寫的,就像CPython是用C編寫的,而Jython是用Java編寫的一樣。

但之前文中不是提到PyPy是用Python編寫的嗎?嗯,這有點簡單。PyPy成為用Python編寫的Python直譯器(而不是RPython)這麼說的原因是RPython使用了與Python相同的語法。

PyPy是怎麼來的?需要解釋以下幾點:

1、它的原始碼是用RPython編寫。

2、RPython轉換工具應用到了程式碼中,從根本上提高了程式碼效率,還可以將程式碼編譯為機器程式碼,這就是Mac,Windows和Linux使用者必須下載不同版本的原因。

3、用上述方式生成的二進位制可執行檔案,就是你執行的Python直譯器。

你不需要執行上述所有這些步驟來使用PyPy。因為已經有提供您安裝和使用的可執行檔案。

此外,由於在框架和實現中使用同一個詞非常令人困惑,PyPy背後的團隊決定放棄這種雙重用法。現在,PyPy僅指Python直譯器,而框架被稱為RPython轉換工具。

接下來,您將瞭解在什麼情況下使用PyPy比Python更好、更快。

Just-In-Time (JIT) 編譯器

在瞭解JIT編譯器的內容之前,讓我們先回顧一下已編譯語言(如C)和解釋語言(如JavaScript)的特性。

在編譯型語言寫的程式執行之前,需要一個專門的編譯過程,把原始碼編譯成機器語言的檔案,如exe格式的檔案,以後要再執行時,直接使用編譯結果即可,如直接執行exe檔案。因為只需編譯一次,以後執行時不需要編譯,所以編譯型語言執行效率高。與特定平臺相關,一般無法移植到其他平臺。如C、C++、Objective等都屬於編譯型語言。

解釋型語言不需要事先編譯,其直接將原始碼解釋成機器碼並立即執行,所以只要某一平臺提供了相應的直譯器即可執行該程式。解釋型語言每次執行都需要將原始碼解釋稱機器碼並執行,效率較低;只要平臺提供相應的直譯器,就可以執行原始碼,所以可以方便源程式移植。

然後還有一些程式語言,例如Python,它混合了編譯和解釋。具體來說,Python首先編譯為位元組碼,然後由CPython解釋。這使程式碼的效能優於用純解釋型語言編寫的程式碼,並保持可移植性優勢。

但是它的效能仍然遠遠低於編譯型語言。其原因是,編譯後的程式碼可以執行許多優化,而位元組碼是不可能的。

這就是JIT編譯器的來源。它試圖通過對機器程式碼進行一些編譯和一些解釋來同時獲得兩種優勢。簡而言之,以下是JIT編譯為提供更快效能所採取的步驟:

1、識別程式碼中最常用的元件,如迴圈中的函式。

2、執行時將這些部件轉換為機器程式碼。

3、優化生成的機器程式碼。

4、用優化的機器程式碼版本取代之前的實現。

還記得教程開頭的兩個巢狀迴圈嗎?PyPy檢測到重複執行相同操作時,將其編譯為機器程式碼,優化機器程式碼,然後轉換實現。這也是為什麼您會看到這樣的結果。

垃圾回收機制

無論何時建立變數、函式或任何其他物件,您的計算機都會給它們分配記憶體。最終,其中一些物件將不再需要。如果不及時清理,計算機可能會耗盡記憶體並使程式崩潰。

在C和C++等程式語言中,通常必須手動處理此問題。其他程式語言(如Python和Java)會自動為您執行此操作。這被稱為自動垃圾回收機制。

CPython使用一種稱為引用計數的技術。實質上,每當引用物件時,Python物件的引用計數都會增加,而在取消引用該物件時則遞減計數。當引用計數為零時,CPython會自動為該物件呼叫記憶體釋放函式。這是一種簡單有效的技術,但有一個陷阱。

當大型物件樹的引用計數變為零時,所有相關物件將被釋放。因此,您可能有很長的暫停時間,在此期間您的程式根本無法執行。

此外,還有一個例子,其中引用計數根本不起作用。如下所示:

class A(object):
  pass
 
a = A()
a.some_property = a
del a

在上面的程式碼中,定義了新的類,然後,建立一個例項,並將其指定為其自身的屬性。最後,刪除例項。

此時,例項將不再可訪問。但是,引用計數不會從記憶體中刪除例項,因為它具有對自身的引用,因此引用計數不是零。此問題被稱為引用迴圈,無法使用引用計數解決。

這是CPython使用的另一個工具,稱為迴圈垃圾回收器。它從已知根(如型別物件)開始遍歷記憶體中的所有物件。然後,它標識所有可訪問的物件,並釋放不可訪問的物件,因為它們不再存在。這樣就解決了引用迴圈問題。但是,當記憶體中存在大量物件時,它可能會建立更明顯的暫停。

另一方面,PyPy不使用引用計數。相反,它只使用第二種技術,即迴圈查詢器。也就是說,它會定期從根開始遍歷活動物件。這使PyPy比CPython具有一些優勢,因為它不需要考慮引用計數,從而使記憶體管理花費的總時間少於CPython。

此外,PyPy將工作拆分為可變數量的部分,並執行每個部分,直到沒有剩餘部分為止。此方法只在每個次要集合之後新增幾毫秒,而不像CPython那樣一次新增數百毫秒。

垃圾回收機制非常複雜,並且有許多超出本教程範圍的內容。您可以在文件中找到有關PyPy垃圾回收機制的詳細資訊。

PyPy的侷限性

PyPy並非萬能,它不是一個適合您所有任務的工具。它甚至可能使應用程式的執行速度比CPython慢得多。這就是為什麼您必須記住以下侷限性。

它不適用於C擴充套件

PyPy最適合純Python應用程式。無論何時使用C擴充套件模組,它的執行速度都要比在CPython中慢得多。原因是PyPy無法優化C擴充套件模組,因為它們不受完全支援。此外,PyPy必須模擬程式碼中的引用計數,使其更慢。

在這種情況下,PyPy團隊建議去掉CPython擴充套件並將其替換為純Python版本。如果不行的話,則必須使用CPython。

儘管如此,核心團隊正在處理C擴充套件。有些軟體包已被移植到PyPy,並且工作速度也同樣快。

它只適用於長時間執行的程式

想象一下你想去一家離你家很近的商店。您既可以直接走路前往,也可以開車。

您的車明顯比您的腳快得多。但是,請考慮需要您完成的步驟:

1.去你的車庫。

2、開車。

3、給車預熱。

4、開車去商店。

5、尋找停車位。

6、在返回途中重複此過程。

開車需要一系列麻煩的步驟,如果你想去的地方就在附近,那就不一定值得了。

現在想想,如果你想去50公里外的鄰近城市,會發生什麼?開車去那裡肯定是值得的,而不是步行去。

雖然速度上的對比並不像上面的類比那樣明顯,但PyPy和CPython和這個道理一樣。

當使用PyPy執行指令碼時,它會執行許多操作以使程式碼執行得更快。如果指令碼本身很簡單,則實際指令碼執行速度會低於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")

當您使用PyPy執行它時,開始時會有一個小延遲,而CPython會立即執行它。在Mac Book Pro上執行它,用CPython需要0.0004873276秒,用PyPy需要0.0019447803秒。

它不執行提前編譯

正如您在本教程開頭所看到的,PyPy不是一個完全編譯型的Python實現。它編譯Python程式碼,但不是Python程式碼的編譯器。由於Python固有的一些特性,導致無法將Python編譯為獨立的二進位制檔案並重用它。

Py Py比完全解釋型的語言快,但比完全編譯的語言(如C)慢。

總結

PyPy是CPython的一種快速且功能強大的替代方案。使用它執行指令碼,您可以在不更改程式碼的情況下大大提高速度。但它也不是萬能的,有一些侷限性。

到此這篇關於如何用PyPy讓你的Python程式碼執行得更快的文章就介紹到這了,更多相關Python PyPy 內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!