爲何Python如此慢

Python當前人氣暴漲。它在DevOps,數據科學,Web開發和安全領域均有使用。javascript

可是在速度方面沒有贏得美譽。html

這裏有關於Python比較其餘語言如,Java, C#, Go, JavaScript, C++進行性能對比,其中Python是最慢的。包含了JIT(C#, Java)和AOT(C,C++)編譯器,也有像解釋型語言如JavaScript。java

注意:文章中我所提到的"Python"均指使用C語言實現的CPython。python

爲何要比其餘語言慢到2-10x的速度?git

這是相關緣由:github

  • 它的GIL(Global Interpreter Lock)
  • 由於是解釋器型非編譯型
  • 由於是動態語言

那這些緣由中哪一個佔最大成分呢?web

它的GIL

現代計算機大多數都具有多核,有時還有多處理器。爲了充分利用這些處理能力,操做系統底層提供了一個叫作線程的東西,它(例如Chrome瀏覽器)能夠系統內容建立多個線程進行指令處理。也就是說當一個進程是CPU密集型,那就能夠經過多個核協同工做來提升應用的運行速度。算法

我本地Chrome瀏覽器當前會開啓44個線程,在不一樣的操做系統例如POSIX(Mac OS和Linux)和Windows提供的線程API結構不同。操做系統來負責線程的調度。編程

若是你以前並無進行過多線程編程,你須要熟悉一個叫作鎖的概念。不像單線程進行,你須要確保在內存改變一個變量時,多個線程不會同時進行操做。瀏覽器

CPython建立變量時,它會開闢內存,而後計算有多少引用該變量,這個概念叫作引用計數。若是引用技術爲0時,它會將內存釋放回給系統。這也就是建立臨時變量,進行循環操做時並不會耗光內存

在多線程共享變量時,CPython時如何對引用計數上鎖呢。這裏就是「全局解釋鎖(global interpreter lock)」負責作的事情,無論你有多少的線程,解釋器在同一個時間只能有一個線程進行操做。

那麼它對Python應用有什麼性能影響

若是應用是單解釋器,單線程。在速度上沒有任何影響。

若是你想在單解釋器使用線程了實現併發操做,而且它們是IO密集型(例如網絡IO或者硬盤IO),那你將會看到GIL相似以下競爭執行:

alt

如你有一個web應用(例如Django)而且使用WSGI,每一個請求將會分配到單獨Python解釋器,此時一個請求只有一把鎖。由於Python解釋器啓動比較慢,一些WSGI會實現爲"Daemon Mode"執行,

那其餘Python運行時呢

PyPy實現的GIL一般要比CPython快3x倍。

Jython沒有GIL,由於Jython的線程受益與JVM的內存管理機制。

JavaScript 是怎麼實現的

首先,JavaScript使用的是標記清除算法實現的垃圾回收機制。而CPython須要GIL主要緣由就是內存管理算法。

JavaScript沒有GIL,可是由於它設計的就是單線程,因此它並不須要。JavaScript的event loop和Promise/Callback機制來進行異步編碼實現併發。Python也有相似的asyncio的event-loop機制。

由於它是解釋型語言

若是你在終端使用python myscript.py運行,CPython將會進行一系列的讀取,詞法分析,語法分析,編譯,解釋和執行代碼。

一個很是重要點事,在編譯過程當中生成的.pyc文件,Python3是放置在__pycache__目錄下,Python2是在文件相同目錄下。該文件就是Python裏面的字節碼,在執行文件不會生成,只會在倒入的模塊或者第三方模塊生成。

因此,Python解釋成字節碼而且進行運行。與Java和C#.NET相比:

Java 編譯成一箇中間語言,而後JVM加載字節碼,進行just in time編譯成機器碼。.NET CLI也是一樣的方式,.NET common language runtime使用just in time編譯成機器碼

那麼,Python爲何要比Java和C#測試性能差那麼多,都是使用字節碼,區別就在於JIT編譯方式。

Just in time須要一箇中間語言容許代碼被拆爲多個chunks(或者frames),AOT編譯器設計用來確保CPU可以理解裏面的內容。

JIT自己沒有提升代碼執行,可是由於它執行仍然是字節碼。而後,JIT容許在運行中優化執行。一個好的JIT加應用程序執行很高的代碼,將字節碼直接優化爲機器碼,從而提升執行效率,這種技術成爲 Hot Spot。

也就是說在程序一次次執行過程當中,會變得愈來愈快。還有就是,Java和C#是強類型語言,因此優化器可以進一步優化。

PyPy也有JIT,設計比CPython執行更快。

那麼CPython爲何不用JIT

JIT也有缺點,就是減慢了啓動時間。CPython啓動時間已經很慢了,PyPy更是比CPython慢2-3x倍。JVM啓動速度是臭的不行。.NET CLR在系統啓動的時候就先啓動了。

若是你有一個Python進程要長時間運行,那麼可使用JIT的hot spots帶來的益處。

然而,CPython設計爲通用語言。因此,當你在實現命令行應用時,每次都要長時間等待JIT啓動那是很是討厭的事情。

CPython也作不少嘗試,也嘗試性使用Plugging方式加入JIT,可是項目如今是停滯的。

由於它是動態類型的

在靜態類型語言中,你必須在申明時就定義它的類型,這些語言有C,C++,Java,C#和Go。

在一個動態類型元中,雖然也有類型的概念,可是一個變量的類型是動態的

a = 1
a = "foo"

在上面示例中,Python使用了相同的變量賦於了不一樣的str類型,它會釋放第一次建立的內存。

靜態語言並非設計來讓你編碼頭疼,而是爲了適應CPU的操做方式。由於全部的操做都是二進制操做,你須要將全部的對象和類型轉換爲低級別的數據結構。

Python已經爲了作了這件事情,你看不見也不須要關係。

不用申明類型並非致使Python變慢的直接緣由,這樣設計讓你幾乎全部操做都是動態的。你能夠在運行時替換對象的方法,你能夠在運行時對底層系統調用進行monkey-patch操做,一切皆有可能。

這樣的設計致使Python很難進行優化。

爲了驗證個人觀點,我將在個人Ubuntu系統中使用syscall追蹤工具Dtrace。CPython並不內建DTrace,因此你須要從新編譯CPython,我將使用Python 3.7.0:

./configure --with-dtrace
make

如今代碼中就可使用Dtrace進行追蹤了,你能夠下載工具來對Python的函數調用,執行時間,CPU時間,syscalls等進行分析:

sudo dtrace -s toolkit/<tracer>.d -c ‘../cpython/python.exe script.py’

py_callflow追蹤器打印了相似以下信息:

alt

因此,Python動態類型是否讓它變慢呢

  • 對比和轉換類型代價很高,每次變量讀取,類型檢查時都很耗時。
  • 動態類型很難對語言進行優化。Python其餘替代方案要快,是由於它們爲達性能對靈活性作出了妥協。
  • 看看Cython,將C靜態類和和Python優化類型能夠在84x性能提升。

結論

Python主要慢的緣由是由於它的動態和靈活性。它能夠做爲解決大多數問題的工具,也存在更優更快的可選方案。

能夠在應用程序中利用async,理解性能工具,考慮使用多解釋器等進行優化。

若是對於啓動時間不是那麼關心的話能夠考慮使用JIT,例如PyPy。

對於性能要求比較苛刻的,你可使用更多的靜態類型變量,考慮使用Cython

相關文章
相關標籤/搜索