Cython的簡單使用


from:http://www.cnblogs.com/freeweb/p/6548208.html

補充:在我mac上的加速效果:

最初:
runing1 time: 0.233466 s
runing2 time: 0.930724 s
使用Cython編譯:
runing1 time: 0.178522 s
runing2 time: 0.646462 s
使用cdef等靜態變量:
runing1 time: 0.001126 s
runing2 time: 0.595804 s
使用c庫:
runing1 time: 0.001176 s
runing2 time: 0.211764 s

  Cython是一個快速生成Python擴展模塊的工具,從語法層面上來講是Python語法和C語言語法的混血,當Python性能遇到瓶頸時,Cython直接將C的原生速度植入Python程序,這樣使Python程序無需使用C重寫,能快速整合原有的Python程序,這樣使得開發效率和執行效率都有很大的提高,而這些中間的部分,都是Cython幫我們做了,接下來簡單說一下Cython的安裝和使用方法

  一、首先Cython官網地址是:http://cython.org/ 這里有cython的安裝和開發文檔,關於Cython的下載可以在pypi上直接下載安裝包:https://pypi.python.org/pypi/Cython/ 由於是在Linux下安裝使用,這里下載的是Cython-0.25.2.tar.gz,上傳至linux執行如下步驟安裝:

tar -xvzf Cython-0.25.2.tar.gz cd Cython-0.25.2 python setup.py install

  這樣Cython模塊就安裝成功了

  然后我們首先看一下cython的基本使用,首先 mkdir hello_pack && cd hello_pack 建立一個目錄並且進入,然后編寫一個hello.pyx的腳本,代碼如下:

# coding=utf-8 def print_hello(name): print "Hello %s!" % name

  代碼很簡單,就是一個函數,然后編寫一個setup.py,代碼如下:

復制代碼
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("hello",["hello.pyx"])] setup( name = "Hello pyx", cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules )
復制代碼

  這里導入了Cython的模塊,其中setup name指定模塊的名稱,然后執行編譯命令:

python setup.py build_ext --inplace

  編譯完之后,看到當前目錄下會生成兩個文件,一個是hello.c一個是hello.so,hello.c就是轉換而成的c代碼,而hello.so就是我們需要的python經過Cython編譯之后的模塊,我們為了當前目錄可被調用,建立__init__.py內容如下:

# coding=utf-8 from hello import *

  然后執行 cd .. 回到上層目錄,建立一個hello.py,代碼如下:

#!/usr/bin/python
# coding=utf-8 from hello_pack import hello hello.print_hello("cython")

  很簡單,就是調用我們編譯好的hello模塊,然后執行里面的方法,現在直接執行hello.py,看到輸出結果正常

  

 

  那么現在,我們就完成了Cython的基本使用,其實setup.py編譯腳本也可以寫成如下這樣:

from distutils.core import setup from Cython.Build import cythonize setup( name='Hello pyx', ext_modules=cythonize('hello.pyx') )

  這種寫法更通用,編譯的時候直接使用 python setup.py build 就可以了,注意不是install,執行install后會把模塊復制到python系統目錄下了;

  執行完之后,會看到當前目錄有一個build目錄,然后進去會發現有如下兩個目錄:

  

  第二個temp是編譯過程中匯編生成的.o文件,第一個lib開頭的目錄中存放的就是hello.so文件,直接把這個文件拷貝出來就可以使用了,另外為了方便,運行腳本直接和so文件放在一個目錄下就可以,直接使用import hello即可導入,也不用建立__init__.py了

  二、以上就是基本的Cython使用,下面借助一個案例來說明Cython和Python程序有哪些性能方面的提升

  首先我們編寫一個compute.py,封裝了兩個計算的方法,代碼如下:

復制代碼
# coding=utf-8 import math def spherical_distance(lon1, lat1, lon2, lat2): radius = 3956 x = math.pi/180.0 a = (90.0 - lat1)*x b = (90.0 - lat2)*x theta = (lon2 - lon1)*x distance = math.acos(math.cos(a)*math.cos(b)) + (math.sin(a) * math.sin(b) * math.cos(theta)) return radius * distance def f_compute(a, x, N): s = 0 dx = (x - a)/N for i in range(N): s += ((a + i * dx) ** 2 - (a + i * dx)) return s * dx
復制代碼

  第一個方法可以計算地球表面任意兩個經緯度之間的距離,這個方法使用了很多三角函數,這些三角函數由python的math模塊提供;第二個方法就是純數字的計算

  那么現在編寫一個執行腳本來調用函數,test.py代碼如下:

復制代碼
#!/usr/bin/env python # coding=utf-8 import compute import time lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826 start_time = time.clock() compute.f_compute(3.2, 6.9, 1000000) end_time = time.clock() print "runing1 time: %f s" % (end_time - start_time) start_time = time.clock() for i in range(1000000): compute.spherical_distance(lon1, lat1, lon2, lat2) end_time = time.clock() print "runing2 time: %f s" % (end_time - start_time)
復制代碼

  代碼就是分別執行100000次數字計算和距離計算,並打印之間,執行結果如下:

  

  可以看到數字計算耗時0.33s,距離計算耗時1.34s,然后我們嘗試直接用Cython模塊進行編譯

  首先復制出來一份 cp compute.py compute1.pyx 然后編寫setup1.py

from distutils.core import setup from Cython.Build import cythonize setup( name='compute_module', ext_modules=cythonize('compute1.pyx'), )

  寫完之后直接執行 python setup1.py build 編譯,編譯完之后導入compute1.so這個模塊,現在只需要修改頭部import compute為import compute1 as compute這樣能保證下面調用代碼不變,然后執行,結果如下:

  

  現在會發現時間比剛才快了一點,說明性能有所提升,然后繼續進行優化,這次全部變量都使用靜態類型,即變量類型提前定義,compute2.pyx代碼如下:

復制代碼
# coding=utf-8 import math cpdef float spherical_distance(float lon1, float lat1, float lon2, float lat2): cdef float radius = 3956 cdef float pi = 3.14159265 cdef float x = pi/180.0 cdef float a,b,theta,distance a = (90.0 - lat1)*x b = (90.0 - lat2)*x theta = (lon2 - lon1)*x distance = math.acos(math.cos(a)*math.cos(b)) + (math.sin(a) * math.sin(b) * math.cos(theta)) return radius * distance def f_compute(double a, double x, int N): cdef int i cdef double s = 0 cdef double dx = (x - a)/N for i in range(N): s += ((a + i * dx) ** 2 - (a + i * dx)) return s * dx
復制代碼

  現在可以看到類型全部做了定義,在cython里面,類型定義使用cdef float這種方式進行;對於方法來說,如果模塊內部相互調用那么同樣使用cdef double這種的方式來定義,如果這個方法要在我們外部執行腳本中調用,那么要么是python原生方法不做任何修改,要么寫成cpdef float這種類型的形式,方法定義類型可以提高效率,但是提高不大,但是變量靜態類型可以極大的提高效率,原因是參與計算的主要是變量;假如一個函數被頻繁調用,那么有必要使用cdef或者cpdef來定義;現在同樣方法編譯compute2模塊,然后在test.py中調用,執行結果如下:

  

  現在發現純數字計算的時間幾乎變成了0秒!而第二個用了0.81s,比剛才快了,但是似乎並不是很快,原因仔細想想會發現,這里面調用了很多三角函數,使用的還是python內置的math模塊,這里可以用C語言的math.h來代替,所以再寫一個compute3.pyx如下:

復制代碼
# coding=utf-8 #import math cdef extern from "math.h": float cosf(float theta) float sinf(float theta) float acosf(float theta) def spherical_distance(float lon1, float lat1, float lon2, float lat2): cdef float radius = 3956 cdef float pi = 3.14159265 cdef float x = pi/180.0 cdef float a,b,theta,distance a = (90.0 - lat1)*x b = (90.0 - lat2)*x theta = (lon2 - lon1)*x distance = acosf(cosf(a)*cosf(b)) + (sinf(a) * sinf(b) * cosf(theta)) return radius * distance def f_compute(double a, double x, int N): cdef int i cdef double s = 0 cdef double dx = (x - a)/N for i in range(N): s += ((a + i * dx) ** 2 - (a + i * dx)) return s * dx
復制代碼

  這次我們屏蔽了自身的math模塊,然后調用了C的頭文件math.h,並引入了cosf,sinf,acosf,實際上這三個函數返回的都是float類型,和我們定義的一致,如果我們想使用double類型,那么應該直接使用cos,sin,acos函數,並且我們可以對這些函數再包裝,這樣cpdef就派上用場了,比如下面:

cdef extern from "math.h": double cos(double) double sin(double) cpdef double tangent(double x): return sin(x)/cos(x)

  比如這里定義了一個測試的tangent正切函數,就是上面這樣寫;同樣我們現在編譯compute3.pyx並測試,結果如下:

  

  現在第一個仍然是接近於0s完成,而第二個三角函數換成C的之后,性能有的非常大的提升,可以說幾乎接近於原生C語言的效率了,這樣看來從Python到Cython性能得到了很大的優化,但是代碼量確差別沒有用C重寫那么大

  根據上面的案例,我們知道在數字,浮點數等計算中Cython可以極大的提高性能,而這方面多線程幾乎不能提高任何性能,有時候反而會降低;但是對於io密集型的場合,用Cython基本上也沒有性能上太大的提升,而多線程的將擁有更加出色的性能,所以Cython應該專注與計算方面的優化;總結一下也就是對於IO密集型應用,優化可以考慮使用多線程或者多進程方式,對於計算密集型的場合,遇到瓶頸時可以考慮使用Cython或者封裝C相關的模塊

  最后,雖然Cython非常好用,但也不能瘋狂的使用,推薦一句名言:我們應該忘記小的效率,過早的優化是一切罪惡的根源,有 97% 的案例如此。簡單來說就是選擇恰當的時機進行優化


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM