Python越來越受歡迎。它被用于DevOps、數(shù)據(jù)科學(xué)、Web開發(fā)和安全。
然而,它并沒有贏得任何速度獎牌。
就速度而言,Java與C或c++或c#或Python相比如何?
答案在很大程度上取決于您正在運行的應(yīng)用程序的類型。沒有一個基準測試是完美的,但是計算機語言基準測試游戲是一個很好的起點。
十多年來,我一直在參考計算機語言基準測試游戲;與其他語言如Java、c#、Go、JavaScript、c++相比,Python是最慢的語言之一。這包括JIT(c#,Java)和AOT(C,c++)編譯器,以及解釋語言,如JavaScript。
注:當(dāng)我說“Python”時,我指的是該語言的參考實現(xiàn)CPython。Python是一門語言,有語法等規(guī)范。但是落實到具體實現(xiàn)上,就不一樣了。用C實現(xiàn)的叫CPython,也是目前的參考實現(xiàn)。即最新的語言特性都是在這個上面先實現(xiàn),Linux,OSX等自帶的也是這個版本。用.NET實現(xiàn)的叫IronPython,Java的叫Jython,用Python實現(xiàn)的叫PyPy
我想回答這個問題:當(dāng)Python比另一種語言慢2-10倍完成一個可比較的應(yīng)用程序時,為什么它慢,我們不能使它更快?
以下是最熱門的理論:
它是GIL(全局解釋器鎖)"因為它是解釋過的而不是編譯過的因為它是動態(tài)類型語言
這些原因中哪一個對性能影響最大?
我們逐個分析
1.它是GIL(全局解釋器鎖)
現(xiàn)代計算機的CPU是多核的,有時也有多個處理器。為了利用所有這些額外的處理能力,操作系統(tǒng)定義了一個稱為線程的底層結(jié)構(gòu),其中一個進程(如Chrome瀏覽器)可以衍生多個線程,并在內(nèi)部為系統(tǒng)提供指令。通過這種方式,如果一個進程是cpu密集型的,那么可以跨內(nèi)核共享負載,從而有效地使大多數(shù)應(yīng)用程序更快地完成任務(wù)。
如果您以前沒有做過多線程編程,那么您需要快速熟悉鎖的概念。與單線程進程不同,您需要確保在更改內(nèi)存中的變量時,多個線程不會嘗試同時訪問/更改相同的內(nèi)存地址。
當(dāng)CPython創(chuàng)建變量時,它分配內(nèi)存,然后計算有多少對該變量的引用存在,這是一個稱為引用計數(shù)的概念。如果引用的數(shù)量為0,那么它將從系統(tǒng)中釋放那塊內(nèi)存。這就是為什么在for循環(huán)的范圍內(nèi)創(chuàng)建“臨時”變量不會增加應(yīng)用程序的內(nèi)存消耗。
當(dāng)變量在多個線程中共享時,挑戰(zhàn)就變成了CPython如何鎖定引用計數(shù)。有一個“全局解釋器鎖”,它小心地控制線程的執(zhí)行。解釋器一次只能執(zhí)行一個操作,不管它有多少線程。
那么其他Pythonruntimes呢?
PyPy有一個GIL,它通常比CPython快3倍。
Jython沒有GIL,因為Jython中的Python線程由Java線程表示,并且受益于JVM內(nèi)存管理系統(tǒng)。
JavaScript是如何做到這一點的?
首先,所有Javascript引擎都使用標(biāo)記-清除垃圾收集。如前所述,GIL的主要需求是CPython的內(nèi)存管理算法。
JavaScript沒有GIL,但它也是單線程的,所以不需要GIL。JavaScript的事件循環(huán)和承諾/回調(diào)模式是實現(xiàn)異步編程而不是并發(fā)的方式。Python對異步事件循環(huán)也有類似的處理。
2.因為這是一種解釋語言
我經(jīng)常聽到這種說法,我發(fā)現(xiàn)這是對CPython實際工作方式的一種粗略簡化。如果您在終端上編寫了pythonmyscript.py,那么CPython將開始一長串的讀取、詞法分析、解析、編譯、解釋和執(zhí)行這些代碼
在這個過程中很重要的一點是創(chuàng)建一個.pyc文件,在編譯器階段,字節(jié)碼序列被寫到Python3上的_pycache__/中的一個文件中,或者在Python2的相同目錄中。這不僅適用于您的腳本,還適用于您導(dǎo)入的所有代碼,包括第三方模塊。
所以大多數(shù)時候(除非您編寫的代碼只運行一次?),Python都是解釋字節(jié)碼并在本地執(zhí)行它。與Java和c#.NET相比:Java編譯成“中間語言”,Java虛擬機讀取字節(jié)碼并及時將其編譯成機器碼。netCIL是一樣的,.net公共語言運行時(CLR)對機器代碼使用即時編譯。
那么,如果Python都使用虛擬機和某種字節(jié)碼,那么為什么在基準測試中它比Java和c#慢那么多呢?
首先,.net和Java是jit編譯的。JIT或即時編譯需要一種中間語言來允許將代碼分割成塊(或幀)。提前(AOT)編譯器的設(shè)計是為了確保CPU在進行任何交互之前能夠理解代碼中的每一行。
JIT本身并沒有使執(zhí)行變得更快,因為它仍然在執(zhí)行相同的字節(jié)碼序列。但是,JIT允許在運行時進行優(yōu)化。一個好的JIT優(yōu)化器會看到應(yīng)用程序的哪些部分被頻繁地執(zhí)行,稱之為“熱點”。然后,它將對這些代碼進行優(yōu)化,用更高效的版本替換它們。
這意味著當(dāng)您的應(yīng)用程序一次又一次地做同樣的事情時,它可以顯著地更快。另外,請記住Java和c#是強類型語言,因此優(yōu)化器可以對代碼進行更多的假設(shè)。
PyPy有一個JIT,正如前一節(jié)所提到的,它比CPython要快得多。
那么為什么CPython不使用JIT呢?
jit也有缺點:其中之一就是啟動時間。CPython的啟動時間已經(jīng)比較慢了,PyPy比CPython慢2-3倍。眾所周知,Java虛擬機的啟動速度很慢。netCLR通過在系統(tǒng)啟動時啟動來解決這個問題,但是CLR的開發(fā)人員還開發(fā)運行CLR的操作系統(tǒng)。
如果您有一個運行了很長時間的Python進程,其中的代碼可以進行優(yōu)化,因為它包含“熱點”,那么JIT就很有意義。
然而,CPython是一種通用實現(xiàn)。因此,如果您正在使用Python開發(fā)命令行應(yīng)用程序,那么每次調(diào)用CLI時都必須等待JIT啟動,這將是非常慢的。
CPython必須嘗試并服務(wù)盡可能多的用例。在CPython中插入JIT是有可能的,但是這個項目在很大程度上已經(jīng)停止了。如果您希望獲得JIT的好處,并且有適合它的工作負載,那么可以使用PyPy。
3.因為它是動態(tài)類型語言
態(tài)類型”語言中,必須在聲明變量時指定變量的類型。包括C,c++,Java,c#,Go。在動態(tài)類型語言中,仍然有類型的概念,但是變量的類型是動態(tài)的。
在這個例子中,Python創(chuàng)建了第二個具有相同名稱和str類型的變量,并釋放為a的第一個實例創(chuàng)建的內(nèi)存
靜態(tài)類型語言的設(shè)計并不是為了讓您的工作變得困難,而是因為CPU的操作方式。如果最終需要將所有操作都等同于簡單的二進制操作,則必須將對象和類型轉(zhuǎn)換為低級數(shù)據(jù)結(jié)構(gòu)。
Python為您做了這些,您只是從來沒有見過它,也不需要關(guān)心它。
不需要聲明類型并不是使Python變慢的原因,Python語言的設(shè)計使您能夠使幾乎任何東西都是動態(tài)的。您可以在運行時替換對象上的方法,您可以在運行時對低級系統(tǒng)調(diào)用的值進行monkey-patch。幾乎一切皆有可能。
正是這種設(shè)計使得優(yōu)化Python變得非常困難。
那么,Python的動態(tài)類型會使它變慢嗎?
比較和轉(zhuǎn)換類型的成本很高,每次讀取、寫入或引用某個變量時,都要檢查該類型很難優(yōu)化一門如此動態(tài)的語言。Python的許多替代品之所以如此之快,是因為它們在性能的名義下對靈活性做出了妥協(xié)看看Cython,它結(jié)合了C-Static類型和Python來優(yōu)化已知類型的代碼,可以提供84x的性能改進。結(jié)論
Python的主要缺點是它的動態(tài)性和通用性。它可以作為解決各種問題的工具,在這些問題中,可能有更優(yōu)化、更快的替代方案。
但是,可以通過利用異步、理解分析工具和考慮使用多解釋器來優(yōu)化Python應(yīng)用程序。
對于啟動時間不重要且代碼有利于JIT的應(yīng)用程序,可以考慮使用PyPy。
對于您的代碼中性能非常重要并且有更多靜態(tài)類型變量的部分,可以考慮使用Cython。
以上內(nèi)容為大家介紹了為什么python這么慢,希望對大家有所幫助,如果想要了解更多Python相關(guān)知識,請關(guān)注IT培訓(xùn)機構(gòu):千鋒教育。http://m.2667701.com/