歡迎光臨
每天分享高質量文章

給力!Cython三分鐘入門教程

我最喜歡的是Python,它的程式碼優雅而實用,可惜純粹從速度上來看它比大多數語言都要慢。大多數人也認為的速度和易於使用是兩極對立的——編寫C程式碼的確非常痛苦。而 Cython 試圖消除這種兩重性,並讓你同時擁有 Python 的語法和 C 資料型別和函式——它們兩個都是世界上最好的。請記住,我絕不是我在這方面的專家,這是我的第一次Cython真物體驗的筆記:


編輯:根據一些我收到的反饋,大家似乎有點混淆——Cython是用來生成 C 擴充套件到而不是獨立的程式的。所有的加速都是針對一個已經存在的 Python 應用的一個函式進行的。沒有使用C 或 Lisp 重寫整個應用程式,也沒有手寫C擴充套件 。只是用一個簡單的方法來整合C的速度和C資料型別到 Python 函式中去。


現在可以說,我們能使下文的great_circle 函式更快。所謂great_circle 是計算沿地球錶面兩點之間的距離的問題:


import math

 

def great_circle(lon1,lat1,lon2,lat2):

    radius = 3956 #miles

    x = math.pi/180.0

 

    a = (90.0lat1)*(x)

    b = (90.0lat2)*(x)

    theta = (lon2lon1)*(x)

    c = math.acos((math.cos(a)*math.cos(b)) +

                  (math.sin(a)*math.sin(b)*math.cos(theta)))

    return radius*c


讓我們呼叫它 50 萬次並測定它的時間 :


import timeit  

 

lon1, lat1, lon2, lat2 = –72.345, 34.323, –61.823, 54.826

num = 500000

 

t = timeit.Timer(“p1.great_circle(%f,%f,%f,%f)” % (lon1,lat1,lon2,lat2),

                       “import p1”)

print “Pure python function”, t.timeit(num), “sec”


約2.2秒 。它太慢了!


讓我們試著快速地用Cython改寫它,然後看看是否有差別:


import math

 

def great_circle(float lon1,float lat1,float lon2,float lat2):

    cdef float radius = 3956.0

    cdef float pi = 3.14159265

    cdef float x = pi/180.0

    cdef float a,b,theta,c

 

    a = (90.0lat1)*(x)

    b = (90.0lat2)*(x)

    theta = (lon2lon1)*(x)

    c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))

    return radius*c


請註意,我們仍然importmath——cython讓您在一定程度上混搭Python和C資料型別在。轉換是自動的,但並非沒有代價。在這個例子中我們所做的就是定義一個Python函式,宣告它的輸入引數是浮點數型別,併為所有變數宣告型別為C浮點資料型別。計算部分它仍然使用了Python的 math 模組。


現在我們需要將其轉換為C程式碼再編譯為Python擴充套件。完成這一部的最好的辦法是編寫一個名為setup.py釋出指令碼。但是,現在我們用手工方式 ,以瞭解其中的巫術:


# this will create a c1.c file – the C source code to build a python extension  

cython c1.pyx  

  

# Compile the object file  

gcc –c –fPIC –I/usr/include/python2.5c1.c  

  

# Link it into a shared library  

gcc –shared c1.o –o c1.so


現在你應該有一個c1.so(或.dll)檔案,它可以被Python import。現在執行一下:


    t = timeit.Timer(“c1.great_circle(%f,%f,%f,%f)” % (lon1,lat1,lon2,lat2),

                     “import c1”)

    print “Cython function (still using python math)”, t.timeit(num), “sec”


約1.8秒 。並沒有我們一開始期望的那種大大的效能提升。使用 python 的 math 模組應該是瓶頸。現在讓我們使用C標準庫替代之:


cdef extern from “math.h”:

    float cosf(float theta)

    float sinf(float theta)

    float acosf(float theta)

 

def great_circle(float lon1,float lat1,float lon2,float lat2):

    cdef float radius = 3956.0

    cdef float pi = 3.14159265

    cdef float x = pi/180.0

    cdef float a,b,theta,c

 

    a = (90.0lat1)*(x)

    b = (90.0lat2)*(x)

    theta = (lon2lon1)*(x)

    c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))

    return radius*c


與 import math 相應,我們使用cdef extern 的方式使用從指定頭檔案宣告函式(在此就是使用C標準庫的math.h)。我們替代了代價高昂的的Python函式,然後建立新的共享庫,並重新測試


    t = timeit.Timer(“c2.great_circle(%f,%f,%f,%f)” % (lon1,lat1,lon2,lat2),

                     “import c2”)

    print “Cython function (using trig function from math.h)”, t.timeit(num), “sec”


現在有點喜歡它了吧?0.4秒 –比純Python函式有5倍的速度增長。我們還有什麼方法可以再提高速度?c2.great_circle()仍是一個Python函式呼叫,這意味著它產生Python的API的開銷(構建引數元組等),如果我們可以寫一個純粹的C函式的話,我們也許能夠加快速度。


cdef extern from “math.h”:  

    float cosf(float theta)  

    float sinf(float theta)  

    float acosf(float theta)  

  

cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):  

    cdef float radius = 3956.0  

    cdef float pi = 3.14159265  

    cdef float x = pi/180.0  

    cdef float a,b,theta,c  

  

    a = (90.0lat1)*(x)  

    b = (90.0lat2)*(x)  

    theta = (lon2lon1)*(x)  

    c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))  

    return radius*c  

  

def great_circle(float lon1,float lat1,float lon2,float lat2,int num):  

    cdef int i  

    cdef float x  

    for i from 0 < = i num:  

        x = _great_circle(lon1,lat1,lon2,lat2)  

    return x


請註意,我們仍然有一個Python函式( def ),它接受一個額外的引數 num。這個函式裡的迴圈使用for i from 0 < = i< num: ,而不是更Pythonic,但慢得多的for i in range(num):。真正的計算工作是在C函式(cdef)中進行的,它傳回float型別。這個版本只要0.2秒——比原先的Python函式速度提高10倍。


為了證明我們所做的已經足夠最佳化,可以用純C寫一個小應用,然後測定時間:


#include

  

#include   

#define NUM 500000  

  

float great_circle(float lon1, float lat1, float lon2, float lat2){  

    float radius = 3956.0;  

    float pi = 3.14159265;  

    float x = pi/180.0;  

    float a,b,theta,c;  

  

    a = (90.0lat1)*(x);  

    b = (90.0lat2)*(x);  

    theta = (lon2lon1)*(x);  

    c = acos((cos(a)*cos(b)) + (sin(a)*sin(b)*cos(theta)));  

    return radius*c;  

}  

  

int main() {  

    int i;  

    float x;  

    for (i=0; i < = NUM; i++)  

        x = great_circle(72.345, 34.323, –61.823, 54.826);  

    printf(“%f”, x);  

}


用gcc -lm -octest ctest.c編譯它,測試用time./ctest …大約0.2秒 。這使我有信心,我Cython擴充套件相對於我的C程式碼也極有效率(這並不是說我的C程式設計能力很弱)。


能夠用 cython 最佳化多少效能通常取決於有多少迴圈,數字運算和Python函式呼叫,這些都會讓程式變慢。已經有一些人報告說在某些案例上 100 至 1000 倍的速度提升。至於其他的任務,可能不會那麼有用。在瘋狂地用 Cython重寫 Python 程式碼之前,記住這一點:


“我們應該忘記小的效率,過早的最佳化是一切罪惡的根源,有 97% 的案例如此。“——DonaldKnuth


換句話說,先用 Python 編寫程式,然後看它是否能夠滿足需要。大多數情況下,它的效能已經足夠好了……但有時候真的覺得慢了,那就使用分析器找到瓶頸函式,然後用cython重寫,很快就能夠得到更高的效能。


外部連結


WorldMill(http://trac.gispython.org/projects/PCL/wiki/WorldMill)——由Sean Gillies 用 Cython 編寫的一個快速的,提供簡潔的 python 介面的模組,封裝了用以處理向量地理空間資料的 libgdal 庫。


編寫更快的 Pyrex 程式碼(http://www.sagemath.org:9001/WritingFastPyrexCode)——Pyrex,是 Cython 的前身,它們有類似的標的和語法。

來源:laiyonghao 

blog.csdn.net/gzlaiyonghao/article/details/4561611



————近期開班————

馬哥聯合BAT、豆瓣等一線網際網路Python開發達人,根據目前企業需求的Python開發人才進行了深度定製,加入了大量一線網際網路公司:大眾點評、餓了麼、騰訊等生產環境真是專案,課程由淺入深,從Python基礎到Python高階,讓你融匯貫通Python基礎理論,手把手教學讓你具備Python自動化開發需要的前端介面開發、Web框架、大監控系統、CMDB系統、認證堡壘機、自動化流程平臺六大實戰能力,讓你從0開始蛻變成Hold住年薪20萬的Python自動化開發人才

10期面授班:2018年03月05號(北京)

09期網路班:騰訊課堂隨到隨學網路

掃描二維碼領取學習資料

更多Python好文請點選【閱讀原文】哦

↓↓↓

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖