crazy曉彤
先說結論:Python是一門動態類型的解釋語言,且有全局解釋器鎖,這三點的底層實現導致了Python比Java、C++慢。
背景知識
01001000 00000001 11000011
計算機CPU只能接受上面這樣的01二進制語言,0和1用來控制高低電位。這樣的0個代碼被稱為機器碼。
C、C++等編譯型語言依靠編譯器將源代碼轉化為機器碼後才能運行,Python、Java等解釋型語言使用解釋器將源代碼翻譯後在虛擬機上執行。對於Python,由於解釋器的存在,其執行效率比C語言慢幾倍甚至幾十倍。
上圖展示各個語言在不同任務上的速度。C語言經過幾十年的發展,優化已經達到了極致。以C語言為基準,大多數解釋語言,如Python、R會慢十倍甚至一百倍。Julia這個解釋語言是個“奇葩”,因為它採用了JIT編譯技術。
解決Python執行效率低的問題,一種解決辦法是使用C/C++語言重寫Python函數,但是這要求程序員對C/C++語言熟悉,且調試速度慢,不適合絕大多數Python程序員。另外一種非常方便快捷的解決辦法就是使用Just-In-Time(JIT)技術。
Python是一種解釋語言
Python是一門解釋語言,Python為我們提供了基於硬件和操作系統的一個虛擬機,並使用解釋器將源代碼轉化為虛擬機可執行的字節碼。字節碼在虛擬機上執行,得到結果。
我們使用python example.py來執行一份源代碼時,Python解釋器會在後臺啟動一個字節碼編譯器(Bytecode Compiler),將源代碼轉換為字節碼,這個過程被稱為編譯階段。字節碼是一種只能運行在虛擬機上的文件,Python的字節碼默認後綴為.pyc,Python生成.pyc後一般放在內存中繼續使用,並不是每次都將.pyc文件保存到磁盤上。有時候我們會看到自己Python代碼文件夾裡有很多.pyc文件與.py文件同名,但也有很多時候看不到.pyc文件。pyc字節碼通過Python虛擬機與硬件交互。虛擬機的出現導致程序和硬件之間增加了中間層,運行效率大打折扣。相信使用過虛擬機軟件的朋友深有體會,在原生的系統上安裝一個虛擬機軟件,在虛擬機上再運行一個其他系統,經常感覺速度下降,體驗變差,這與Python虛擬機導致程序運行慢是一個原理。
Just-In-Time(JIT)技術為解釋語言提供了一種優化,它能克服上述效率問題,極大提升代碼執行速度,同時保留Python語言的易用性。使用JIT技術時,JIT編譯器將Python源代碼編譯成機器直接可以執行的機器語言,並可以直接在CPU等硬件上運行。這樣就跳過了原來的虛擬機,執行速度幾乎與用C語言編程速度並無二致。
Python是動態類型的
原生Python速度慢的另一個重要原因是變量類型不確定,即動態類型。Python聲明一個變量的語法很簡單,如a = 1,但沒有指定a到底是一個整數和一個浮點小數,甚至還可以把a賦值為一個字符串。C++和Java中,變量都是有類型的,比如在C++中,聲明一個整數。
int a = 1;計算機在執行代碼時,必須知道是什麼類型的,才能給這個變量分配合適的內存,調用相應的指令。Python解釋器要進行大量的類型推斷,推斷出一個變量是什麼類型,轉換成計算機可執行的機器碼,這個過程會非常耗時。這個推斷的過程發生在將.py文件換換成
.pyc
字節碼的編譯階段。
與Python不同,C/C++等編譯型語言要提前把整個程序先編譯好,再執行可執行文件。而且只需要編譯一次,後面再使用這個程序時就不需要編譯了。
Python編譯和運行都慢
一個程序的運行時間一般是編譯時間加運行時間。
Python動態類型的特點導致編譯時間較長,基於解釋器的特點導致其運行時間較長,加起來導致總時間比C++和Java慢。
因此,Python非常容易上手,極大方便了開發人員,相應地,給程序員節省下來的時間就丟給了Python解釋器,導致Python執行程序的非常慢。
Python 全局解釋器鎖
其實Python只是一種計算機語言,計算機語言可以有不同的解釋器,我們常用的Python一般是CPython這個解釋器實現。CPython有全局解釋器鎖(GIL),這個鎖限制了Python使用多核CPU的並行處理能力。關於多核CPU如何工作,可以參見我的頭條文章:https://www.toutiao.com/i6714432785378968067/
突破這個鎖並不難,可以使用Python提供的multiprocessing庫來使用多核。
總結
Python是一門動態類型的解釋語言,且有全局解釋器鎖,這三點的底層實現導致了Python比Java、C++慢。