作爲語言黑馬的python,爲什麼能這麼慢?

如果你對這些過程感興趣,可以看看我之前寫的文章:

6分鐘修改Python語言:https://hackernoon.com/modifying-the-python-language-in-7-minutes-b94b0a99ce14

這個過程的重點就是它會在編譯階段生成.pyc文件,字節碼會寫到__pycache__/下的文件中(如果是Python 3),或者寫到與源代碼同一個目錄中(Python 2)。不僅你編寫的腳本是這樣,所有你導入的代碼都是這樣,包括第三方模塊。

因此絕大多數情況下(除非你寫的代碼只會運行一次),Python是在解釋字節碼並在本地執行。與Java和C#.NET比較一下:

Java將源代碼編譯成“中間語言”,然後Java虛擬機讀取字節碼並即時編譯成機器碼。.NET CIL也是一樣的,.NET的公共語言運行時(CLR)使用即時編譯將字節碼編譯成機器碼。

那麼,既然它們都使用虛擬機,以及某種字節碼,為什麼Python在性能測試中比Java和C#慢那麼多?第一個原因是,.NET和Java是即時編譯的(JIT)。

即時編譯,即JIT(Just-in-time),需要一種中間語言,將代碼分割成小塊(或者稱幀)。而提前編譯(Ahead of Time,簡稱AOT)是編譯器把源代碼翻譯成CPU能理解的代碼之後再執行。

JIT本身並不能讓執行更快,因為它執行的是同樣的字節碼序列。但是,JIT可以在運行時做出優化。好的GIT優化器能找到應用程序中執行最多的部分,稱為“熱點”。然後對那些字節碼進行優化,將它們替換成效率更高的代碼。

這就是說,如果你的應用程序會反覆做某件事情,那麼速度就會快很多。此外,別忘了Java和C#都是強類型語言,所以優化器可以對代碼做更多的假設。

前面說過,PyPy有個JIT,因此它比CPython要快很多。下面這篇性能測試的文章介紹得更詳細:

哪個版本的Python最快?https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b

那麼為什麼CPython不用JIT?

JIT也有缺點:首先就是啟動速度。CPython的啟動速度已經比較慢了,而PyPy的啟動速度要比CPython慢兩到三倍。Java虛擬機的啟動速度也是出了名的慢。.NET CLR在系統啟動時啟動,因此避免了這個問題,但這要歸功於CLR和操作系統是同一撥開發者開發的。

如果你有一個Python進程需要運行很長時間,而且代碼裡包含“熱點”可以被優化,那麼使用JIT就很不錯。

但是,CPython是個通用的實現。因此如果要用Python開發命令行程序,那麼每次都要等待JIT調用CLI就特別慢了。

CPython試圖滿足大部分情況下的需求。有一個在CPython中實現JIT(https://www.slideshare.net/AnthonyShaw5/pyjion-a-jit-extension-system-for-cpython)的項目,不過這個項目已經停止很久了。

如果你想要享受JIT的好處,並且要處理的任務適合JIT,那就使用PyPy。

“因為它是動態類型語言”

“靜態類型”語言要求必須在變量定義時指定其類型,例如C、C++、Java、C#和Go等。

而動態類型語言中儘管也有類型的概念,但變量的類型是動態的。

作為語言黑馬的python,為什麼能這麼慢?

在這個例子中,Python用相同的名字和str類型定義了第二個變量,同時釋放了第一個a的實例佔用的內存。

靜態類型語言的設計目的並不是折磨人,這樣設計是因為CPU就是這樣工作的。如果任何操作最終都要轉化成簡單的二進制操作,那就需要將對象和類型都轉換成低級數據結構。

Python幫你做了這一切,只不過你從來沒有關心過,也不需要關心。

不需要定義類型並不是Python慢的原因。Python的設計可以讓你把一切都做成動態的。你可以在運行時替換對象的方法,可以在運行時給底層系統調用打補丁。幾乎一切都有可能。

而這種設計使得Python的優化變得很困難。

為了演示這個觀點,我使用了一個Mac OS下的系統調用跟蹤工具,叫做Dtrace。CPython的發佈並不支持DTrace,因此需要重新編譯CPython。演示中用的是Python 3.6.6:

作為語言黑馬的python,為什麼能這麼慢?

現在Python.exe的代碼中包含了Dtrace的跟蹤代碼。Paul Ross有一篇非常好的關於DTrace的演講(https://github.com/paulross/dtrace-py#the-lightning-talk)。可以從這裡下載DTrace用於Python的文件(https://github.com/paulross/dtrace-py/tree/master/toolkit)用來測量函數調用、執行時間、CPU時間、系統調用以及各種函數等等。

作為語言黑馬的python,為什麼能這麼慢?

py_callflow跟蹤器會顯示應用程序的所有函數調用。

作為語言黑馬的python,為什麼能這麼慢?

那麼,Python的動態類型是否讓Python更慢?

比較並轉換類型的代價很大。每次讀取、寫入或引用變臉時都會檢查類型

動態類型的語言很難優化。許多替代Python的語言很快的原因就是它們犧牲了便利性來交換性能。

例如Cython(http://cython.org/),它通過結合C的靜態類型和Python的方式,使得代碼中的類型已知,從而優化代碼,能夠獲得84倍的性能提升(http://notes-on-cython.readthedocs.io/en/latest/std_dev.html)

結論

Python慢的主要原因是因為它的動態和多樣性。它能用於解決各種問題,但多數問題都有優化得更好和更快的解決方案。

但Python應用也有許多優化措施,如使用異步、理解性能測試工具,以及使用多解釋器等。

對於啟動時間不重要,而代碼可能享受到JIT的好處的應用,可以考慮使用PyPy。

對於代碼中性能很重要的部分,如果變量大多是靜態類型,可以考慮使用Cython。


分享到:


相關文章: