您好,登錄后才能下訂單哦!
本篇內容介紹了“Python為什么這么慢”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Python 現在越來越火,已經迅速擴張到包括 DevOps、數據科學、Web 開發、信息安全等各個領域當中。
然而,相比起 Python 擴張的速度,Python 代碼的運行速度就顯得有點遜色了。
在代碼運行速度方面,Java、C、C++、C# 和 Python 要如何進行比較呢?并沒有一個放之四海而皆準的標準,因為具體結果很大程度上取決于運行的程序類型,而語言基準測試可以作為衡量的一個方面。
根據我這些年來進行語言基準測試的經驗來看,Python 比很多語言運行起來都要慢。無論是使用 JIT 編譯器的 C#、Java,還是使用 AOT 編譯器的 C、C++,又或者是 JavaScript 這些解釋型語言,Python 都比它們運行得慢。
注意:對于文中的 “Python” ,一般指 CPython 這個官方的實現。當然我也會在本文中提到其它語言的 Python 實現。
我要回答的是這個問題:對于一個類似的程序,Python 要比其它語言慢 2 到 10 倍不等,這其中的原因是什么?又有沒有改善的方法呢?
主流的說法有這些:
“是
全局解釋器鎖(GIL)的原因”
“是因為 Python 是解釋型語言而不是編譯型語言”
“是因為 Python 是一種動態類型的語言”
哪一個才是是影響 Python 運行效率的主要原因呢?
現在很多計算機都配備了具有多個核的 CPU ,有時甚至還會有多個處理器。為了更充分利用它們的處理能力,操作系統定義了一個稱為線程的低級結構。某一個進程(例如 Chrome 瀏覽器)可以建立多個線程,在系統內執行不同的操作。在這種情況下,CPU 密集型進程就可以跨核心分擔負載了,這樣的做法可以大大提高應用程序的運行效率。
例如在我寫這篇文章時,我的 Chrome 瀏覽器打開了 44 個線程。需要提及的是,基于 POSIX 的操作系統(例如 Mac OS、Linux)和 Windows 操作系統的線程結構、API 都是不同的,因此操作系統還負責對各個線程的調度。
如果你還沒有寫過多線程執行的代碼,你就需要了解一下線程鎖的概念了。多線程進程比單線程進程更為復雜,是因為需要使用線程鎖來確保同一個內存地址中的數據不會被多個線程同時訪問或更改。
CPython 解釋器在創建變量時,首先會分配內存,然后對該變量的引用進行計數,這稱為引用計數。如果變量的引用數變為 0,這個變量就會從內存中釋放掉。這就是在 for 循環代碼塊內創建臨時變量不會增加內存消耗的原因。
而當多個線程內共享一個變量時,CPython 鎖定引用計數的關鍵就在于使用了 GIL,它會謹慎地控制線程的執行情況,無論同時存在多少個線程,解釋器每次只允許一個線程進行操作。
如果你的程序只有單線程、單進程,代碼的速度和性能不會受到全局解釋器鎖的影響。
但如果你通過在單進程中使用多線程實現并發,并且是 IO 密集型(例如網絡 IO 或磁盤 IO)的線程,GIL 競爭的效果就很明顯了。
由 David Beazley 提供的 GIL 競爭情況圖http://dabeaz.blogspot.com/2010/01/python-gil-visualized.html
對于一個 web 應用(例如 Django),同時還使用了 WSGI,那么對這個 web 應用的每一個請求都運行一個單獨的 Python 解釋器,而且每個請求只有一個鎖。同時因為 Python 解釋器的啟動比較慢,某些 WSGI 實現還具有“守護進程模式”,可以使 Python 進程一直就緒。
PyPy 也是一種帶有 GIL 的解釋器,但通常比 CPython 要快 3 倍以上。
Jython 則是一種沒有 GIL 的解釋器,這是因為 Jython 中的 Python 線程使用 Java 線程來實現,并且由 JVM 內存管理系統來進行管理。
所有的 Javascript 引擎使用的都是 mark-and-sweep 垃圾收集算法,而 GIL 使用的則是 CPython 的內存管理算法。
JavaScript 沒有 GIL,而且它是單線程的,也不需要用到 GIL, JavaScript 的事件循環和 Promise/Callback 模式實現了以異步編程的方式代替并發。在 Python 當中也有一個類似的 asyncio 事件循環。
我經常會聽到這個說法,但是這過于粗陋地簡化了 Python 所實際做的工作了。其實當終端上執行 python myscript.py
之后,CPython 會對代碼進行一系列的讀取、語法分析、解析、編譯、解釋和執行的操作。
如果你對這一系列過程感興趣,也可以閱讀一下我之前的文章:在 6 分鐘內修改 Python 語言 。
.pyc
文件的創建是這個過程的重點。在代碼編譯階段,Python 3 會將字節碼序列寫入 __pycache__/
下的文件中,而 Python 2 則會將字節碼序列寫入當前目錄的 .pyc
文件中。對于你編寫的腳本、導入的所有代碼以及第三方模塊都是如此。
因此,絕大多數情況下(除非你的代碼是一次性的……),Python 都會解釋字節碼并本地執行。與 Java、C#.NET 相比:
Java 代碼會被編譯為“中間語言”,由 Java 虛擬機讀取字節碼,并將其即時編譯為機器碼。.NET CIL 也是如此,.NET CLR(Common-Language-Runtime)將字節碼即時編譯為機器碼。
既然 Python 像 Java 和 C# 那樣都使用虛擬機或某種字節碼,為什么 Python 在基準測試中仍然比 Java 和 C# 慢得多呢?首要原因是,.NET 和 Java 都是 JIT 編譯的。
即時(JIT)編譯需要一種中間語言,以便將代碼拆分為多個塊(或多個幀)。而提前(AOT)編譯器則需要確保 CPU 在任何交互發生之前理解每一行代碼。
JIT 本身不會使執行速度加快,因為它執行的仍然是同樣的字節碼序列。但是 JIT 會允許在運行時進行優化。一個優秀的 JIT 優化器會分析出程序的哪些部分會被多次執行,這就是程序中的“熱點”,然后優化器會將這些代碼替換為更有效率的版本以實現優化。
這就意味著如果你的程序是多次重復相同的操作時,有可能會被優化器優化得更快。而且,Java 和 C# 是強類型語言,因此優化器對代碼的判斷可以更為準確。
PyPy 使用了明顯快于 CPython 的 JIT。更詳細的結果可以在這篇性能基準測試文章中看到:哪一個 Python 版本最快?。
JIT 也不是***的,它的一個顯著缺點就在于啟動時間。 CPython 的啟動時間已經相對比較慢,而 PyPy 比 CPython 啟動還要慢 2 到 3 倍。Java 虛擬機啟動速度也是出了名的慢。.NET CLR 則通過在系統啟動時啟動來優化體驗,而 CLR 的開發者也是在 CLR 上開發該操作系統。
因此如果你有個長時間運行的單一 Python 進程,JIT 就比較有意義了,因為代碼里有“熱點”可以優化。
不過,CPython 是個通用的實現。設想如果使用 Python 開發命令行程序,但每次調用 CLI 時都必須等待 JIT 緩慢啟動,這種體驗就相當不好了。
CPython 試圖用于各種使用情況。有可能實現將 JIT 插入到 CPython 中,但這個改進工作的進度基本處于停滯不前的狀態。
如果你想充分發揮 JIT 的優勢,請使用 PyPy。
在 C、C++、Java、C#、Go 這些靜態類型語言中,必須在聲明變量時指定變量的類型。而在動態類型語言中,雖然也有類型的概念,但變量的類型是可改變的。
a = 1a = "foo"
在上面這個示例里,Python 將變量 a
一開始存儲整數類型變量的內存空間釋放了,并創建了一個新的存儲字符串類型的內存空間,并且和原來的變量同名。
靜態類型語言這樣的設計并不是為了為難你,而是為了方便 CPU 運行而這樣設計的。因為最終都需要將所有操作都對應為簡單的二進制操作,因此必須將對象、類型這些高級的數據結構轉換為低級數據結構。
Python 也實現了這樣的轉換,但用戶看不到這些轉換,也不需要關心這些轉換。
不用必須聲明類型并不是為了使 Python 運行慢,Python 的設計是讓用戶可以讓各種東西變得動態:可以在運行時更改對象上的方法,也可以在運行時動態添加底層系統調用到值的聲明上,幾乎可以做到任何事。
但也正是這種設計使得 Python 的優化異常的難。
為了證明我的觀點,我使用了一個 Mac OS 上的系統調用跟蹤工具 DTrace。CPython 發布版本中沒有內置 DTrace,因此必須重新對 CPython 進行編譯。以下以 Python 3.6.6 為例:
wget https://github.com/python/cpython/archive/v3.6.6.zipunzip v3.6.6.zipcd v3.6.6./configure --with-dtracemake
這樣 python.exe
將使用 DTrace 追蹤所有代碼。Paul Ross 也作過關于 DTrace 的閃電演講。你可以下載 Python 的 DTrace 啟動文件來查看函數調用、執行時間、CPU 時間、系統調用,以及各種其它的內容。
sudo dtrace -s toolkit/<tracer>.d -c ‘../cpython/python.exe script.py’
py_callflow
追蹤器顯示了程序里調用的所有函數。
那么,Python 的動態類型會讓它變慢嗎?
類型比較和類型轉換消耗的資源是比較多的,每次讀取、寫入或引用變量時都會檢查變量的類型
Python 的動態程度讓它難以被優化,因此很多 Python 的替代品能夠如此快都是為了提升速度而在靈活性方面作出了妥協
而 Cython 結合了 C 的靜態類型和 Python 來優化已知類型的代碼,它可以將性能提升 84 倍。
“Python為什么這么慢”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。