【Python】淺談 字節碼 & 虛擬機 (Python 解釋器)

目錄

一、緒論

二、說明

2.1 字節碼編譯

2.2 Python 虛擬機 (PVM)

2.3 性能意義 ☆

2.4 開發意義

三、小結


一、緒論

Python 通常被描述爲一種解釋語言,在這類語言中,源代碼在程序運行時被“翻譯”成“指令”,但這還不確切。同許多解釋型語言一樣,Python 在正式處理代碼前,內部先進行預處理,將 Python 源代碼編譯成字節碼,然後將其轉發至 Python 虛擬機中。換言之,Python 實際上將源代碼編譯爲虛擬機的一組指令,而這種中間格式的指令稱爲“字節碼”。

二、說明

2.1 字節碼編譯

執行 Python 程序時,Python 內部將隱式地將 源代碼 (source code) 編譯成 字節碼 (byte code)。編譯 (compile) 可以理解爲一種簡單的“翻譯”步驟 (類比於 C、C++ 等靜態語言的編譯,但結果有別);字節碼則是一種 低級的與平臺無關的 代碼表現形式,有別於機器的二進制代碼 (如 Intel 或 ARM 芯片的指令)。簡言之,Python 將源代碼語句的每一條分解爲單一步驟,從而“翻譯”爲一組字節碼指令。相比於文本文件中的 Python 源代碼,字節碼的運行速度更快。

注意,上述過程對大多數用戶而言是隱式的。若 Python 進程在機器上擁有寫入權限, 那麼它將把程序的字節碼保存爲一個後綴爲 .pyc 的文件 (即已經編譯 (compiled) 的 .py) 。在 Python3 中,.pyc 字節碼文件被存儲在名爲 __pycache__ 的子目錄中,該子目錄位於與 .py 源文件相同的路徑下。而 __pycache__ 子目錄中的文件命名中包含了編譯它們的 Python 的版本信息 (基於創建它的特定 Python 進制代碼版本,如 test.cpython-36.pyc) 。例如:

在 Windows 下創建一個 test.py 文件:

然後在 IDE 中編譯 (後續說明原因),在 test.py 所在目錄下可以顯式看到多出了一個 __pycache__ 子目錄:

進入 __pycache__ 子目錄中可以看到其字節碼文件  test.cpython-36.pyc:

在記事本中打開,可見由於編碼問題無法直接閱讀,但可以看到 test.py 的程序主體也在其中:

事實上,當一個模塊首次被導入或修改已編譯的源文件時,都會在 .py 所在目錄的 __pycache__ 的子目錄下創建一個包含已編譯代碼的 .pyc 文件。新創建的 __pycache__ 子目錄能夠 避免太多文件擠在同一路徑下,而新的字節碼文件命名規範確保了同一主機上安裝的不同版本的 Python 所生成的字節碼文件 不會相互覆蓋。當然,這些字節碼文件都是自動生成的,與大多數 Python 程序無關,也隨着不同版本的 Python 有着不同的形式。

上述 Python 保存字節碼的方式是對 Python 程序啓動速度的一種優化。下次運行程序時,若上次保存字節碼後未再修改過源代碼,並且使用同一個 Python 編譯器版本運行, 那麼 Python 將會加載 .pyc 文件並跳過編譯步驟。該過程工作原理如下:

  • 源文件的改變:Python 會自動檢查源文件和字節碼文件最後一次修改的時間戳,以確認是否必須重新編譯:若源代碼被編輯並保存,則下次程序運行時,將自動重新創建字節碼文件。
  • Python 的版本:導入 (import) 機制會檢查是否需要因使用了不同的 Python 版本而重新編譯,這些版本信息在 Python3 中存儲於字節碼文件名中間部分。

可見,源文件的修改和 Python 版本的改變都會觸發新的字節碼文件的編譯。導入模塊時,若同時存在 .py 和 .pyc 文件,Python 將優先使用 .pyc 文件運行;若 .pyc 文件的編譯時間早於 .py 的時間,則將重新編譯 .py 並更新 .pyc 文件。還有,字節碼文件也是發佈 Python 程序的方法之一。若 Python 只找到 .pyc 字節碼文件,而未找到對應的原始 .py 源代碼文件,它也很“樂意”運行該程序。

即便 Python 無法在機器上寫入字節碼, 程序也可以正常工作 —— 字節碼會在內存中生成, 並在程序結束時直接被丟棄 (這就是在 IDE 中只點擊“運行”不會看到 .pyc 文件的原因,若要顯式查看還需要在 IDE 中手動點擊“編譯”)。然而,由於 .pyc 文件具有加速啓動的作用,最好確保在大型程序中能夠創建它們。

最後,字節碼只會針對那些被導入 (import) 的文件而生成, 而不是頂層的執行腳本 (嚴格來說,這是一種針對“導入”的優化)。此外,文件僅在程序運行 (或編譯) 時纔會被導入,而在交互式命令行中輸入的命令並不會生成字節碼。

2.2 Python 虛擬機 (PVM)

一旦源程序編譯成字節碼 (或從已存在的 .pyc 文件中載入字節碼),便會將字節碼發送到被稱爲 Python 虛擬機 (Python Virtual Machine, PVM) 的程序上來執行。不同於大名鼎鼎的 JAVA 虛擬機 JVM,Python 虛擬機 PVM 相對鮮爲人知,其中一個原因在於 PVM 是更爲知名的 Python 解釋器如 CPython 的一部分 (CPython 使用了基於堆棧的虛擬機)。但不同於 Vmware 那種系統虛擬機,此處指的是類似 JVM、CLR 的 程序虛擬機

常見的 Python 解釋器有:

Python 解釋器 由一個 編譯器 和一個 虛擬機 構成,編譯器負責將源代碼轉換成字節碼文件,而虛擬機負責執行字節碼。所以,解釋型語言其實也有編譯過程,只不過該編譯過程並非直接生成目標代碼,而是生成中間代碼 (字節碼),然後再通過虛擬機來逐行解釋執行字節碼 

爲此,PVM 並非一個獨立程序,也無須安裝。本質上,PVM 可以理解爲一個 迭代運行字節碼指令的“大循環”—— 一個接一個地完成操作。PVM 作爲 Python 的運行時引擎,它時常表現爲 Python 系統的一部分,並且作爲實際運行腳本的組件。從程序運行的技術流程上看,PVM 是 Python 解釋器運行程序的最後一步。下圖展示了 Python 運行時的簡化執行模型:

事實上,程序員編寫的由源代碼構成的 .py 源文件,先被自動編譯爲由字節碼構成的 .pyc 字節碼文件,然後被傳遞給 Python 虛擬機 PVM 中運行。注意,.pyc 文件是字節碼在磁盤上的表現形式,而字節碼在 PVM 程序裏對應的是 PyCodeObject 對象 (import 模塊時創建該對象)。PVM 會字節碼當前的上下文環境中,從編譯得到的 PyCodeObject 對象中逐條執行字節碼指令,從而完成程序的執行。

總體上,操作系統中執行程序離不開兩個概念:進程和線程。Python 則通過 PyInterpreterState 和 PyTreadState 分別模擬進程和線程這兩個概念。其中,每個 PyThreadState 都對應着一個幀棧,PVM 在多個線程上切換。當 PVM 開始執行時,它會先進行一些初始化操作,最後進入 PyEval_EvalFramEx 函數,它的作用是不斷讀取編譯好的字節碼並逐條執行,類似 CPU 執行指令的過程。PyEval_EvalFramEx 函數內部主要是一個 switch 結構,根據字節碼的不同執行不同的代碼。

總之,程序員只需簡單地編寫代碼並運行文件,而 Python 會負責所有運行這些文件的邏輯。

2.3 性能意義

倘若熟悉 C 和 C++ 這類完全編譯語言,則很容易發現 Python 運行模式中的一些差異。例如,Python 的執行流程中通常 沒有 build 或 make 的步驟,而是寫完代碼立即執行;Python 字節碼並非機器的二進制代碼 (如 Intel 或 ARM 芯片的指令)。字節碼是特定於 Python 的一種表現形式。這就是爲什麼 Python 代碼總是無法運行得像 C 或 C++ 代碼一樣快,因爲 PVM循環 (而非 CPU 芯片) 仍需逐行解釋字節碼,並且字節碼指令比 CPU 指令需要更多的工作。另一方面,與其他經典的解釋器不同,Python 仍有內部的編譯步驟,即無需反覆地重新分析、分解每行源代碼語句的文本。在上述機制的綜合作用下結果是:Python 代碼的運行速度介於傳統的編譯語言和傳統的解釋語言之間。

2.4 開發意義

Python 執行模型所導致的另一個結果是:Python 的開發和執行環境實際上並無區別。換言之,編譯和執行源代碼的系統是同一個系統。在 Python 中,編譯器總是在運行時出現,並作爲運行程序系統的一部分,從而將大大縮短開發週期,提升開發效率。在程序開始執行前,無需預編譯和鏈接,只需簡單地輸入並運行代碼即可。這同樣讓 Python 帶上了更濃厚的動態語言色彩:在運行時, Python 程序去構建並執行另一個 Python 程序是可能的,且往往非常方便。例如,eval 和 exec 內置模塊能夠接受並運行包含Python 程序代碼的字符串。上述結構是 Python 能夠實現產品定製的原因:因爲 Python 代碼可以動態地被修改,用戶可以改進系統內部的 Python 部分,而無需擁有或編譯整個系統的代碼。

總而言之,Python 完全不需要初始的編譯階段,所有的事情都是在程序運行時發生的,甚至包括建立函數和類的操作以及模塊的鏈接。而這些工作對於靜態語言而言,往往發生在執行之前。

三、小結


 參考文獻:

《Learning Python th》

https://blog.csdn.net/cqcre/article/details/91456902

https://zhuanlan.zhihu.com/p/38855233

https://www.cnblogs.com/webber1992/p/6597166.html

https://www.cnblogs.com/Bottle-cap/articles/10123700.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章