我最喜歡的是Python,它的代碼優(yōu)雅而實(shí)用,可惜純粹從速度上來看它比大多數(shù)語言都要慢。大多數(shù)人也認(rèn)為的速度和易于使用是兩極對立的——編寫C代碼的確非常痛苦。而 Cython 試圖消除這種兩重性,并讓你同時擁有 Python 的語法和 C 數(shù)據(jù)類型和函數(shù)——它們兩個都是世界上最好的。請記住,我絕不是我在這方面的專家,這是我的第一次Cython真實(shí)體驗(yàn)的筆記:
編輯:根據(jù)一些我收到的反饋,大家似乎有點(diǎn)混淆——Cython是用來生成 C 擴(kuò)展到而不是獨(dú)立的程序的。所有的加速都是針對一個已經(jīng)存在的 Python 應(yīng)用的一個函數(shù)進(jìn)行的。沒有使用C 或 Lisp 重寫整個應(yīng)用程序,也沒有手寫C擴(kuò)展 。只是用一個簡單的方法來整合C的速度和C數(shù)據(jù)類型到 Python 函數(shù)中去。
現(xiàn)在可以說,我們能使下文的great_circle 函數(shù)更快。所謂great_circle 是計(jì)算沿地球表面兩點(diǎn)之間的距離的問題:
import math
def great_circle(lon1,lat1,lon2,lat2):
radius=3956#miles
x=math.pi/180.0
a=(90.0-lat1)*(x)
b=(90.0-lat2)*(x)
theta=(lon2-lon1)*(x)
c=math.acos((math.cos(a)*math.cos(b))+
(math.sin(a)*math.sin(b)*math.cos(theta)))
returnradius*c
讓我們調(diào)用它 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(floatlon1,floatlat1,floatlon2,floatlat2):
cdeffloatradius=3956.0
cdeffloatpi=3.14159265
cdeffloatx=pi/180.0
cdeffloata,b,theta,c
a=(90.0-lat1)*(x)
b=(90.0-lat2)*(x)
theta=(lon2-lon1)*(x)
c=math.acos((math.cos(a)*math.cos(b))+(math.sin(a)*math.sin(b)*math.cos(theta)))
returnradius*c
請注意,我們?nèi)匀籭mportmath——cython讓您在一定程度上混搭Python和C數(shù)據(jù)類型在。轉(zhuǎn)換是自動的,但并非沒有代價。在這個例子中我們所做的就是定義一個Python函數(shù),聲明它的輸入參數(shù)是浮點(diǎn)數(shù)類型,并為所有變量聲明類型為C浮點(diǎn)數(shù)據(jù)類型。計(jì)算部分它仍然使用了Python的 math 模塊。
現(xiàn)在我們需要將其轉(zhuǎn)換為C代碼再編譯為Python擴(kuò)展。完成這一部的最好的辦法是編寫一個名為setup.py發(fā)布腳本。但是,現(xiàn)在我們用手工方式 ,以了解其中的巫術(shù):
# this will create a c1.c file - the C source code to build a python extension
cythonc1.pyx
# Compile the object file
gcc-c-fPIC-I/usr/include/python2.5/c1.c
# Link it into a shared library
gcc-sharedc1.o-oc1.so
現(xiàn)在你應(yīng)該有一個c1.so(或.dll)文件,它可以被Python import?,F(xiàn)在運(yùn)行一下:
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 模塊應(yīng)該是瓶頸。現(xiàn)在讓我們使用C標(biāo)準(zhǔn)庫替代之:
cdef externfrom"math.h":
floatcosf(floattheta)
floatsinf(floattheta)
floatacosf(floattheta)
def great_circle(floatlon1,floatlat1,floatlon2,floatlat2):
cdeffloatradius=3956.0
cdeffloatpi=3.14159265
cdeffloatx=pi/180.0
cdeffloata,b,theta,c
a=(90.0-lat1)*(x)
b=(90.0-lat2)*(x)
theta=(lon2-lon1)*(x)
c=acosf((cosf(a)*cosf(b))+(sinf(a)*sinf(b)*cosf(theta)))
returnradius*c
與 import math 相應(yīng),我們使用cdef extern 的方式使用從指定頭文件聲明函數(shù)(在此就是使用C標(biāo)準(zhǔn)庫的math.h)。我們替代了代價高昂的的Python函數(shù),然后建立新的共享庫,并重新測試
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"
現(xiàn)在有點(diǎn)喜歡它了吧?0.4秒 –比純Python函數(shù)有5倍的速度增長。我們還有什么方法可以再提高速度?c2.great_circle()仍是一個Python函數(shù)調(diào)用,這意味著它產(chǎn)生Python的API的開銷(構(gòu)建參數(shù)元組等),如果我們可以寫一個純粹的C函數(shù)的話,我們也許能夠加快速度。
cdef externfrom"math.h":
floatcosf(floattheta)
floatsinf(floattheta)
floatacosf(floattheta)
cdef float_great_circle(floatlon1,floatlat1,floatlon2,floatlat2):
cdef floatradius=3956.0
cdef floatpi=3.14159265
cdef floatx=pi/180.0
cdef floata,b,theta,c
a=(90.0-lat1)*(x)
b=(90.0-lat2)*(x)
theta=(lon2-lon1)*(x)
c=acosf((cosf(a)*cosf(b))+(sinf(a)*sinf(b)*cosf(theta)))
returnradius*c
defgreat_circle(floatlon1,floatlat1,floatlon2,floatlat2,intnum):
cdef inti
cdef floatx
forifrom0< =?inum:
x=_great_circle(lon1,lat1,lon2,lat2)
returnx
請注意,我們?nèi)匀挥幸粋€Python函數(shù)( def ),它接受一個額外的參數(shù) num。這個函數(shù)里的循環(huán)使用for i from 0 < = i< num: ,而不是更Pythonic,但慢得多的for i in range(num):。真正的計(jì)算工作是在C函數(shù)(cdef)中進(jìn)行的,它返回float類型。這個版本只要0.2秒——比原先的Python函數(shù)速度提高10倍。
為了證明我們所做的已經(jīng)足夠優(yōu)化,可以用純C寫一個小應(yīng)用,然后測定時間:
#include
#include
#define NUM 500000
floatgreat_circle(floatlon1,floatlat1,floatlon2,floatlat2){
floatradius=3956.0;
floatpi=3.14159265;
floatx=pi/180.0;
floata,b,theta,c;
a=(90.0-lat1)*(x);
b=(90.0-lat2)*(x);
theta=(lon2-lon1)*(x);
c=acos((cos(a)*cos(b))+(sin(a)*sin(b)*cos(theta)));
returnradius*c;
}
intmain(){
inti;
floatx;
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擴(kuò)展相對于我的C代碼也極有效率(這并不是說我的C編程能力很弱)。
能夠用 cython 優(yōu)化多少性能通常取決于有多少循環(huán),數(shù)字運(yùn)算和Python函數(shù)調(diào)用,這些都會讓程序變慢。已經(jīng)有一些人報(bào)告說在某些案例上 100 至 1000 倍的速度提升。至于其他的任務(wù),可能不會那么有用。在瘋狂地用 Cython重寫 Python 代碼之前,記住這一點(diǎn):
“我們應(yīng)該忘記小的效率,過早的優(yōu)化是一切罪惡的根源,有 97% 的案例如此?!啊狣onaldKnuth
換句話說,先用 Python 編寫程序,然后看它是否能夠滿足需要。大多數(shù)情況下,它的性能已經(jīng)足夠好了……但有時候真的覺得慢了,那就使用分析器找到瓶頸函數(shù),然后用cython重寫,很快就能夠得到更高的性能。
-
python
+關(guān)注
關(guān)注
56文章
4797瀏覽量
84689 -
C數(shù)據(jù)
+關(guān)注
關(guān)注
0文章
2瀏覽量
1812
原文標(biāo)題:給力!Cython三分鐘入門教程
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論