Cython的簡單使用

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

  1、首先Cython官網地址是:http://cython.org/ 這裏有cython的安裝和開發文檔,關於Cython的下載能夠在pypi上直接下載安裝包:https://pypi.python.org/pypi/Cython/ 因爲是在Linux下安裝使用,這裏下載的是Cython-0.25.2.tar.gz,上傳至linux執行以下步驟安裝: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,看到輸出結果正常ui

  

 

  那麼如今,咱們就完成了Cython的基本使用,其實setup.py編譯腳本也能夠寫成以下這樣:spa

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了

  2、以上就是基本的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% 的案例如此。簡單來講就是選擇恰當的時機進行優化

相關文章
相關標籤/搜索