什么是Cython?讓Python有C語言的速度
Python的一個超集,可以編譯為C,Cython結(jié)合了Python的易用性和原生代碼的速度。
Python作為最方便,豐富的配置和徹底有用的編程語言之一而享有盛譽。 但執(zhí)行速度?沒那么快。
讓我們開始了解Cython,Cython語言是Python的一個超集,編譯成C語言,產(chǎn)生的性能提升可以從幾個百分點到幾個數(shù)量級,具體取決于手頭的任務(wù)。 對于受Python原生對象類型約束的工作,加速將不會很大。 但是對于數(shù)值操作,或任何不涉及Python自身內(nèi)部的操作,收益可能是巨大的。 這樣,許多Python的本地限制可以被繞過或完全超越。
使用Cython,你可以避開Python的許多原生限制,或者完全超越Python,而無需放棄Python的簡便性和便捷性。 在本文中,我們將介紹Cython背后的基本概念,并創(chuàng)建一個使用Cython加速功能的簡單Python應(yīng)用程序。
編譯Python到C
Python代碼可以直接調(diào)用C模塊。這些C模塊可以是通用的C庫或?qū)iT為Python工作的庫。Cython生成第二種類型的模塊:與Python內(nèi)部對話的C庫,可以與現(xiàn)有的Python代碼綁定在一起。
Cython代碼在設(shè)計上看起來很像Python代碼。如果你給Cython編譯器提供了一個Python程序,它將會按原樣接受它,但是Cython的原生加速器都不會起作用。但是如果你用Cython的特殊語法來修飾Python代碼,那么Cython就可以用快速的C代替慢的Python對象。
請注意,Cython的方法是漸進的。這意味著開發(fā)人員可以從現(xiàn)有的Python應(yīng)用程序開始,通過對代碼立刻進行更改來加快速度,而不是從頭開始重寫整個應(yīng)用程序。
這種方法通常與軟件性能問題的性質(zhì)相吻合。在大多數(shù)程序中,絕大多數(shù)CPU密集型代碼都集中在一些熱點上,也就是帕累托原則的一個版本,也被稱為“80/20”規(guī)則。因此,Python應(yīng)用程序中的大部分代碼不需要進行性能優(yōu)化,只需要幾個關(guān)鍵部分。你可以逐漸將這些熱點轉(zhuǎn)換為Cython,從而獲得你最需要的性能提升。程序的其余部分可以保留在Python中,以方便開發(fā)人員。
如何使用Cython
下面的代碼來自Cython文檔:
def f(x): return x**2-xdef integrate_f(a, b, N): s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
這是一個例子,一個不完整的函數(shù)的實現(xiàn)。作為純Python代碼,速度很慢,因為Python必須在機器本機數(shù)字類型和其內(nèi)部對象類型之間來回轉(zhuǎn)換。
現(xiàn)在考慮相同代碼的Cython版本,并強調(diào)Cython的增加:
cdef double f(double x): return x**2-xdef integrate_f(double a, double b, int N): cdef int i cdef double s, x, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
如果我們顯式聲明變量類型,無論是函數(shù)參數(shù)還是函數(shù)體(double,int等)中使用的變量,Cython都會將所有這些轉(zhuǎn)換成C語言。我們也可以使用cdef關(guān)鍵字來定義 盡管這些函數(shù)只能被其他的Cython函數(shù)調(diào)用,而不能被Python腳本調(diào)用,但是這些函數(shù)主要是用C實現(xiàn)的。
Cython優(yōu)勢
除了能夠加速已經(jīng)編寫的代碼之外,Cython還具有其他幾個優(yōu)點:
使用外部C庫可以更快
像NumPy這樣的Python軟件包可以在Python界面中打包C庫,使它們易于使用。但是,這些包在Python和C之間來回切換會減慢速度。Cython可以讓你直接與底層庫進行通信,而不需要Python(也支持C ++庫)。
可以同時使用C和Python內(nèi)存管理
如果你使用Python對象,它們就像在普通的Python中一樣被內(nèi)存管理和垃圾收集。但是如果你想創(chuàng)建和管理自己的C級結(jié)構(gòu),并使用malloc/free來處理它們,你可以這樣做,只記得自己清理一下。
可以根據(jù)需要選擇安全性或速度
Cython通過decorator 和編譯器指令(例如@boundscheck(False))自動執(zhí)行對C中彈出的常見問題的運行時檢查,例如對數(shù)組的超出邊界訪問。因此,由Cython生成的C代碼默認比手動C代碼安全得多。
如果確信在運行時不需要這些檢查,則可以在整個模塊上或僅在選擇功能上禁用它們以獲得額外的編譯速度。
Cython還允許本地訪問使用“緩沖協(xié)議”的Python結(jié)構(gòu),以直接訪問存儲在內(nèi)存中的數(shù)據(jù)(無需中間復制)。Cython的“記憶視圖”可以高速地在這些結(jié)構(gòu)上進行工作,并且具有適合任務(wù)的安全級別。
Cython C代碼可以從釋放GIL中受益
Python的全局解釋器鎖(Global Interpreter Lock,GIL)同步解釋器中的線程,保護對Python對象的訪問并管理資源的爭用。但GIL被廣泛批評為Python性能的絆腳石,特別是在多核系統(tǒng)上。
如果有一段代碼不會引用Python對象并執(zhí)行長時間運行,那么可以使用nogil:指令將其標記為允許它在沒有GIL的情況下運行。這使得Python中間人可以做其他事情,并允許Cython代碼使用多個內(nèi)核(附加工作)。
Cython可以使用Python類型的提示語法
Python有一個類型提示語法,主要由linters和代碼檢查器使用,而不是CPython解釋器。 Cython有它自己的代碼裝飾的自定義語法,但是最近修改了Cython,你可以使用Python類型提示語法為Cython提供類型提示。
Cython限制
請記住,Cython不是一個魔術(shù)棒。它不會自動將每一個poky Python代碼變成極速的C代碼。為了充分利用Cython,你必須明智地使用它,并理解它的局限性:
常規(guī)Python代碼的加速很少
當Cython遇到Python代碼時,它不能完全翻譯成C語言,它將這些代碼轉(zhuǎn)換成一系列對Python內(nèi)部的C調(diào)用。這相當于將Python的解釋器從執(zhí)行循環(huán)中提取出來,這使得代碼默認加速了15%到20%。請注意,這是最好的情況。在某些情況下,可能看不到性能改善,甚至性能下降。
原生Python數(shù)據(jù)結(jié)構(gòu)有一點加速
Python提供了大量的數(shù)據(jù)結(jié)構(gòu) - 字符串,列表,元組,字典等等。它們對于開發(fā)者來說非常方便,而且他們自帶了自動內(nèi)存管理功能,但是他們比純C慢。
Cython讓你繼續(xù)使用所有的Python數(shù)據(jù)結(jié)構(gòu),盡管沒有太多的加速。這又是因為Cython只是在Python運行時調(diào)用創(chuàng)建和操作這些對象的C API。因此,Python數(shù)據(jù)結(jié)構(gòu)的行為與Cython優(yōu)化的Python代碼大致相同:有時會得到一個提升,但只有一點。
Cython代碼運行速度最快時,“純C”
如果你在C中有一個標有cdef關(guān)鍵字的函數(shù),那么它的所有變量和內(nèi)聯(lián)函數(shù)調(diào)用都是純C的,所以它的運行速度可以和C一樣快。 但是,如果該函數(shù)引用任何Python原生代碼(如Python數(shù)據(jù)結(jié)構(gòu)或?qū)?nèi)部Python API的調(diào)用),則該調(diào)用將成為性能瓶頸。
幸運的是,Cython提供了一種方法來發(fā)現(xiàn)這些瓶頸:一個源代碼報告,一目了然地顯示您的Cython應(yīng)用程序的哪些部分是純C以及哪些部分與Python交互。 對應(yīng)用程序進行了更好的優(yōu)化,就會減少與Python的交互。
為Cython應(yīng)用程序生成的源代碼報告。 白色區(qū)域純C;黃色區(qū)域顯示與Python內(nèi)部的交互。一個精心優(yōu)化的Cython程序?qū)⒈M可能的黃色。 展開的最后一行顯示了解釋其相應(yīng)Cython代碼的C代碼。
Cython NumPy
Cython改進了基于C的第三方數(shù)字運算庫(如NumPy)的使用。由于Cython代碼編譯為C,它可以直接與這些庫進行交互,并將Python的瓶頸帶出循環(huán)。
但是NumPy特別適用于Cython。 Cython對NumPy中的特定結(jié)構(gòu)具有本地支持,并提供對NumPy數(shù)組的快速訪問。在傳統(tǒng)的Python腳本中使用的熟悉的NumPy語法可以在Cython中使用。
但是,如果要創(chuàng)建Cython和NumPy之間最接近的綁定,則需要使用Cython的自定義語法進一步修飾代碼。例如,cimport語句允許Cython代碼在編譯時在庫中查看C級構(gòu)造,以實現(xiàn)最快的綁定。
由于NumPy被廣泛使用,Cython支持NumPy“開箱即用”。如果你安裝了NumPy,你可以在你的代碼中聲明cimport numpy,然后添加進一步的裝飾來使用暴露的函數(shù)。
Cython分析和性能
可以通過分析代碼并親眼目睹瓶頸在哪里獲得最佳性能。Cython為Python的cProfile模塊提供鉤子,因此可以使用Python自己的分析工具來查看Cython代碼的執(zhí)行情況。無需在工具組之間切換;可以繼續(xù)所熟悉和喜愛的Python世界中工作。
它有助于記住所有情況下,Cython不是魔術(shù),仍然適用明智的現(xiàn)實世界的表現(xiàn)實踐。在Python和Cython之間來回穿梭越少,你的應(yīng)用運行得越快。
例如,如果你有一個你想要在Cython中處理的對象的集合,那么不要在Python中迭代它,并且在每一步調(diào)用一個Cython函數(shù)。將整個集合傳遞給你的Cython模塊并在那里迭代。這種技術(shù)經(jīng)常在管理數(shù)據(jù)的庫中使用,因此這是在自己的代碼中模擬的好模型。
我們使用Python是因為它為程序員提供了便利,并且能夠快速開發(fā)。有時程序員的工作效率是以犧牲性能為代價的。使用Cython,只需要一點點額外的努力就可以給你兩全其美的好處。