更多課程 選擇中心

Python培訓
美國上市教育機構

400-111-8989

Python培訓

為什么Python代碼的運行速度比較慢呢?這會影響Python語言的擴張嗎?

  • 發布:LCTT
  • 來源:Python開發者
  • 時間:2018-12-29 11:51

今天達內Python培訓想要跟大家探討的一個問題是為什么Python代碼的運行比較慢,以及Python代碼的運行速度會影響Python的擴張速度嗎?如果你也對這兩個問題感興趣,那就隨我們一起來看看吧:

Python 現在越來越火,已經迅速擴張到包括 DevOps、數據科學、Web 開發、信息安全等各個領域當中。

然而,相比起 Python 擴張的速度,Python 代碼的運行速度就顯得有點遜色了。

在代碼運行速度方面,Java、C、C++、C# 和 Python 要如何進行比較呢?并沒有一個放之四海而皆準的標準,因為具體結果很大程度上取決于運行的程序類型,而語言基準測試Computer Language Benchmarks Games可以作為衡量的一個方面。

根據我這些年來進行語言基準測試的經驗來看,Python 比很多語言運行起來都要慢。無論是使用 JIT 編譯器的 C#、Java,還是使用 AOT 編譯器的 C、C++,又或者是 JavaScript 這些解釋型語言,Python 都比它們運行得慢。

注意:對于文中的 “Python” ,一般指 CPython 這個官方的實現。當然我也會在本文中提到其它語言的 Python 實現。

我要回答的是這個問題:對于一個類似的程序,Python 要比其它語言慢 2 到 10 倍不等,這其中的原因是什么?又有沒有改善的方法呢?

主流的說法有這些:

“是全局解釋器鎖Global Interpreter Lock(GIL)的原因”

“是因為 Python 是解釋型語言而不是編譯型語言”

“是因為 Python 是一種動態類型的語言”

哪一個才是是影響 Python 運行效率的主要原因呢?

是全局解釋器鎖的原因嗎?

現在很多計算機都配備了具有多個核的 CPU ,有時甚至還會有多個處理器。為了更充分利用它們的處理能力,操作系統定義了一個稱為線程的低級結構。某一個進程(例如 Chrome 瀏覽器)可以建立多個線程,在系統內執行不同的操作。在這種情況下,CPU 密集型進程就可以跨核心分擔負載了,這樣的做法可以大大提高應用程序的運行效率。

例如在我寫這篇文章時,我的 Chrome 瀏覽器打開了 44 個線程。需要提及的是,基于 POSIX 的操作系統(例如 Mac OS、Linux)和 Windows 操作系統的線程結構、API 都是不同的,因此操作系統還負責對各個線程的調度。

如果你還沒有寫過多線程執行的代碼,你就需要了解一下線程鎖的概念了。多線程進程比單線程進程更為復雜,是因為需要使用線程鎖來確保同一個內存地址中的數據不會被多個線程同時訪問或更改。

CPython 解釋器在創建變量時,首先會分配內存,然后對該變量的引用進行計數,這稱為引用計數reference counting。如果變量的引用數變為 0,這個變量就會從內存中釋放掉。這就是在 for 循環代碼塊內創建臨時變量不會增加內存消耗的原因。

而當多個線程內共享一個變量時,CPython 鎖定引用計數的關鍵就在于使用了 GIL,它會謹慎地控制線程的執行情況,無論同時存在多少個線程,解釋器每次只允許一個線程進行操作。

這會對 Python 程序的性能有什么影響?

如果你的程序只有單線程、單進程,代碼的速度和性能不會受到全局解釋器鎖的影響。

但如果你通過在單進程中使用多線程實現并發,并且是 IO 密集型(例如網絡 IO 或磁盤 IO)的線程,GIL 競爭的效果就很明顯了。

為什么Python代碼的運行速度比較慢呢?這會影響Python語言的擴張嗎?

由 David Beazley 提供的 GIL 競爭情況圖http://dabeaz.blogspot.com/2010/01/python-gil-visualized.html

對于一個 web 應用(例如 Django),同時還使用了 WSGI,那么對這個 web 應用的每一個請求都運行一個單獨的 Python 解釋器,而且每個請求只有一個鎖。同時因為 Python 解釋器的啟動比較慢,某些 WSGI 實現還具有“守護進程模式”,可以使 Python 進程一直就緒。

其它的 Python 解釋器表現如何?

PyPy 也是一種帶有 GIL 的解釋器,但通常比 CPython 要快 3 倍以上。

Jython 則是一種沒有 GIL 的解釋器,這是因為 Jython 中的 Python 線程使用 Java 線程來實現,并且由 JVM 內存管理系統來進行管理。

JavaScript 在這方面又是怎樣做的呢?

所有的 Javascript 引擎使用的都是 mark-and-sweep 垃圾收集算法,而 GIL 使用的則是 CPython 的內存管理算法。

JavaScript 沒有 GIL,而且它是單線程的,也不需要用到 GIL, JavaScript 的事件循環和 Promise/Callback 模式實現了以異步編程的方式代替并發。在 Python 當中也有一個類似的 asyncio 事件循環。

是因為 Python 是解釋型語言嗎?

我經常會聽到這個說法,但是這過于粗陋地簡化了 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 編譯的。

即時Just-in-time(JIT)編譯需要一種中間語言,以便將代碼拆分為多個塊(或多個幀)。而提前ahead of time(AOT)編譯器則需要確保 CPU 在任何交互發生之前理解每一行代碼。

JIT 本身不會使執行速度加快,因為它執行的仍然是同樣的字節碼序列。但是 JIT 會允許在運行時進行優化。一個優秀的 JIT 優化器會分析出程序的哪些部分會被多次執行,這就是程序中的“熱點”,然后優化器會將這些代碼替換為更有效率的版本以實現優化。

這就意味著如果你的程序是多次重復相同的操作時,有可能會被優化器優化得更快。而且,Java 和 C# 是強類型語言,因此優化器對代碼的判斷可以更為準確。

PyPy 使用了明顯快于 CPython 的 JIT。更詳細的結果可以在這篇性能基準測試文章中看到:哪一個 Python 版本最快?。

那為什么 CPython 不使用 JIT 呢?

JIT 也不是完美的,它的一個顯著缺點就在于啟動時間。 CPython 的啟動時間已經相對比較慢,而 PyPy 比 CPython 啟動還要慢 2 到 3 倍。Java 虛擬機啟動速度也是出了名的慢。.NET CLR 則通過在系統啟動時啟動來優化體驗,而 CLR 的開發者也是在 CLR 上開發該操作系統。

因此如果你有個長時間運行的單一 Python 進程,JIT 就比較有意義了,因為代碼里有“熱點”可以優化。

不過,CPython 是個通用的實現。設想如果使用 Python 開發命令行程序,但每次調用 CLI 時都必須等待 JIT 緩慢啟動,這種體驗就相當不好了。

CPython 試圖用于各種使用情況。有可能實現將 JIT 插入到 CPython 中,但這個改進工作的進度基本處于停滯不前的狀態。

如果你想充分發揮 JIT 的優勢,請使用 PyPy。

是因為 Python 是一種動態類型的語言嗎?

在 C、C++、Java、C#、Go 這些靜態類型語言中,必須在聲明變量時指定變量的類型。而在動態類型語言中,雖然也有類型的概念,但變量的類型是可改變的。

a = 1

a = "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.zip

unzip v3.6.6.zip

cd v3.6.6

./configure --with-dtrace

make

這樣 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 是一種動態、多功能的語言,因此運行起來會相對緩慢。對于不同的實際需求,可以使用各種不同的優化或替代方案。

例如可以使用異步,引入分析工具或使用多種解釋器來優化 Python 程序。

對于不要求啟動時間且代碼可以充分利用 JIT 的程序,可以考慮使用 PyPy。

而對于看重性能并且靜態類型變量較多的程序,不妨使用 Cython。

感謝您的閱讀, 以上就是今天達內python培訓對“為什么Python代碼的運行速度比較慢呢?這會影響Python語言的擴張嗎?”的一些解讀,你還有別的更獨到的見解嗎?更多Python相關的問題,歡迎您來達內Python培訓機構進行咨詢。

免責聲明:內容和圖片源自網絡,版權歸原作者所有,如有侵犯您的原創版權請告知,我們將盡快刪除相關內容。

預約申請免費試聽課

填寫下面表單即可預約申請免費試聽!怕錢不夠?可就業掙錢后再付學費! 怕學不會?助教全程陪讀,隨時解惑!擔心就業?一地學習,可全國推薦就業!

上一篇:如何快速學好Python編程?這幾條彎路你走了就快不了...
下一篇:Python培訓機構哪家好?如何選擇靠譜的Python培訓機構?

如何快速入門Python編程?這19個語法是第一站!

零基礎學習Python編程的進階之路

參加Python培訓,為什么要選擇達內Python培訓機構呢?

如何自學Python編程?這里有24條建議送給你!

選擇城市和中心
黑龍江省

吉林省

河北省

湖南省

貴州省

云南省

廣西省

海南省

4438全国大成网人网站