代碼運行在IPython-Notebook中,在IPython-Notebook中匯入cython環境。
Cython可以在Python中摻雜C和C++的靜態類型,cython編譯器可以把Cython源碼編譯成C或C++代碼,編譯後的代碼可以單獨執行或者作為Python中的模型使用。Cython中的強大之處在於可以把Python和C結合起來,它使得看起來像Python語言的Cython代碼有著和C相似的運行速度。
我們使用一個簡單的Fibonacci函數來比較下Python和Cython的區別:
123456 |
#pythondef fib1(n): a,b=0.0,1.0 for i in range(n): a,b=a+b,a return a |
下面代碼使用%%cython標誌表示下面的代碼使用cython編譯
1234567 |
%%cythondef fib2(int n): cdef double a=0.0, b=1.0 for i in range(n): a,b = a+b,a return a |
通過比較上面的代碼,為了把Python中的動態類型轉換為Cython中的靜態類型,我們用cdef來定義C語言中的變數i,a,b。
我們用C語言實現Fibonacci函數,然後通過Cython用Python封裝,其中cfib.h為Fibonacci函數C語言實現,如下:
12345678 |
double cfib(int n) { int i; double a=0.0, b=1.0, tmp; for (i=0; i<n; ++i) { tmp = a; a = a + b; b = tmp; } return a;} |
1234567 |
%%cythoncdef extern from "/home/ldy/MEGA/python/cython/cfib.h": double cfib(int n) def fib3(n): """Returns the nth Fibonacci number.""" return cfib(n) |
比較不同方法的已耗用時間:
123456 |
%timeit result=fib1(1000)%timeit result=fib2(1000)%timeit result=fib3(1000) |
10000 loops, best of 3: 73.6 µs per loop1000000 loops, best of 3: 1.94 µs per loop1000000 loops, best of 3: 1.92 µs per loop
Cython代碼的編譯
Cython代碼的編譯為Python可調用模組的過程主要分為兩步:第一步是cython編譯器把Cython代碼最佳化成C或C++代碼;第二步是使用C或C++編譯器編譯產生的C或C++代碼得到Python可調用的模組。
我們通過一個setup.py指令碼來編譯上面寫的fib.pyxCython代碼,如下所示,關鍵就在第三行,cythonize函數的作用是通過cython編譯器把Cython代碼轉換為C代碼,setup函數則是把產生的C代碼轉換成Python可調用模組。
1234 |
from distutils.core import setupfrom Cython.Build import cythonizesetup(ext_modules=cythonize('fib.pyx'))#setup(ext_modules=cythonize('*.pyx','fib1.pyx'))也可以一次編譯多個Cython檔案 |
寫好setup.py檔案後,就可以通過下述命令執行編譯:
1 |
python setup.py build_ext --inplace |
執行後產生了fib.c代碼以及fib.so檔案,以及一些中間結果儲存在build檔案夾裡。
1234 |
import osos.chdir('/home/ldy/MEGA/python/cython/test')os.getcwd()!ls |
build fib.c fib.pyx fib.so setup.py
通過Python調用產出的fib.so模組:
12 |
import fibfib.fib2(90) |
2.880067194370816e+18
Cython中類型的定義
為什麼Cython和Python比會提高很多效能,主要原因有兩點:一是Python是解釋型語言,在運行之前Python解譯器把Python代碼解釋成Python位元組碼運行在Python虛擬機器上,Python虛擬機器把Python位元組碼最終翻譯成CPU能執行的機器碼;而Cython代碼是事先直接編譯成可被Python調用的機器碼,在運行時可直接執行。第二個主要的原因是Python是動態類型,Python解譯器在解釋時需要判斷類型,然後再提取出底層能夠啟動並執行資料以及操作;然而C語言等比較底層的語言是靜態類型,編譯器直接提取資料進行操作產生機器碼。
Cython中使用cdef來定義靜態類型:
123 |
cdef int icdef int jcdef float f |
也可以一次定義多個:
1234 |
cdef: int i int j float f |
Cython中還允許在靜態類型和動態類型同時存在及相互賦值:
123456 |
%%cythoncdef int a=1,b=2,c=3list_of_ints=[a,b,c]list_of_ints.append(4)a=list_of_ints[1]print a,list_of_ints |
2 [1, 2, 3, 4]
聲明Python類型為靜態類型,Cython支援把一些Python內建的如list,tuple,dict等型別宣告為靜態類型,這樣聲明使得它們能像正常Python類型一樣使用,但是需要約束成只能是他們所申明的類型,不能隨意變動。
12345678910111213 |
%%cythoncdef: list names dict name_numname_num={'jerry':1,'Tom':2,'Bell':3}names=list(name_num.keys())print namesother_names=names#動態類型可以從靜態類型的Python對象初始化del other_names[0]#因為引用了同一個list,所以都會刪除第一個元素print names,other_namesother_names=tuple(other_names)#names和other_names的區別在於names只能是list類型,print other_names #other_names可以引用任何類型 |
['Bell', 'jerry', 'Tom']['jerry', 'Tom'] ['jerry', 'Tom']('jerry', 'Tom')
Cython中numpy的使用
我們先構造一個函數來測試下使用純Python時的運算時間來做對比,這個函數的作用是對一副輸入映像求梯度(不必過分關注函數的功能,在這隻是使用這個函數作為測試)。函數的輸入資料是indata一個像素為1400*1600的圖片;輸出為outdata,為每個像素梯度值,下面是這個函數的純Python實現: