Cython 三分鐘入門教程
作者:perrygeo
譯者:賴勇浩(http://laiyonghao.com)
原文:http://www.perrygeo.net/wordpress/?p=116
我最喜歡的是Python,它的代碼優(yōu)雅而實(shí)用,可惜純粹從速度上來(lái)看它比大多數(shù)語(yǔ)言都要慢。大多數(shù)人也認(rèn)為的速度和易于使用是兩極對(duì)立的——編寫C代碼的確非常痛苦。而 Cython 試圖消除這種兩重性,并讓你同時(shí)擁有 Python 的語(yǔ)法和 C 數(shù)據(jù)類型和函數(shù)——它們兩個(gè)都是世界上最好的。請(qǐng)記住,我絕不是我在這方面的專家,這是我的第一次Cython真實(shí)體驗(yàn)的筆記:
編輯:根據(jù)一些我收到的反饋,大家似乎有點(diǎn)混淆——Cython是用來(lái)生成 C 擴(kuò)展到而不是獨(dú)立的程序的。所有的加速都是針對(duì)一個(gè)已經(jīng)存在的 Python 應(yīng)用的一個(gè)函數(shù)進(jìn)行的。沒有使用 C 或 Lisp 重寫整個(gè)應(yīng)用程序,也沒有手寫C擴(kuò)展 。只是用一個(gè)簡(jiǎn)單的方法來(lái)整合C的速度和C數(shù)據(jù)類型到 Python 函數(shù)中去。
現(xiàn)在可以說,我們能使下文的 great_circle 函數(shù)更快。所謂 great_circle 是計(jì)算沿地球表面兩點(diǎn)之間的距離的問題:
p1.py
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)))
return radius*c
讓我們調(diào)用它 50 萬(wàn)次并測(cè)定它的時(shí)間 :
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秒 。它太慢了!
讓我們?cè)囍焖俚赜肅ython改寫它,然后看看是否有差別:
c1.pyx
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.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)))
return radius*c
請(qǐng)注意,我們?nèi)匀籭mport math——cython讓您在一定程度上混搭Python和C數(shù)據(jù)類型在。轉(zhuǎn)換是自動(dòng)的,但并非沒有代價(jià)。在這個(gè)例子中我們所做的就是定義一個(gè)Python函數(shù),聲明它的輸入?yún)?shù)是浮點(diǎn)數(shù)類型,并為所有變量聲明類型為C浮點(diǎn)數(shù)據(jù)類型。計(jì)算部分它仍然使用了Python的 math 模塊。
現(xiàn)在我們需要將其轉(zhuǎn)換為C代碼再編譯為Python擴(kuò)展。完成這一部的最好的辦法是編寫一個(gè)名為setup.py發(fā)布腳本。但是,現(xiàn)在我們用手工方式 ,以了解其中的巫術(shù):
# 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.5/ c1.c
# Link it into a shared library
gcc -shared c1.o -o c1.so
現(xiàn)在你應(yīng)該有一個(gè)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), "s
約1.8秒 。并沒有我們一開始期望的那種大大的性能提升。使用 python 的 match 模塊應(yīng)該是瓶頸?,F(xiàn)在讓我們使用C標(biāo)準(zhǔn)庫(kù)替代之:
c2.pyx
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.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
return radius*cec"
與 import math 相應(yīng),我們使用cdef extern 的方式使用從指定頭文件聲明函數(shù)(在此就是使用C標(biāo)準(zhǔn)庫(kù)的math.h)。我們替代了代價(jià)高昂的的Python函數(shù),然后建立新的共享庫(kù),并重新測(cè)試:
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倍的速度增長(zhǎng)。我們還有什么方法可以再提高速度?c2.great_circle()仍是一個(gè)Python函數(shù)調(diào)用,這意味著它產(chǎn)生Python的API的開銷(構(gòu)建參數(shù)元組等),如果我們可以寫一個(gè)純粹的C函數(shù)的話,我們也許能夠加快速度。
c3.pyx
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.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(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
請(qǐng)注意,我們?nèi)匀挥幸粋€(gè)Python函數(shù)( def ),它接受一個(gè)額外的參數(shù) num。這個(gè)函數(shù)里的循環(huán)使用for i from 0 < = i < num: ,而不是更Pythonic,但慢得多的for i in range(num):。真正的計(jì)算工作是在C函數(shù)(cdef)中進(jìn)行的,它返回float類型。這個(gè)版本只要0.2秒——比原先的Python函數(shù)速度提高10倍。
為了證明我們所做的已經(jīng)足夠優(yōu)化,可以用純C寫一個(gè)小應(yīng)用,然后測(cè)定時(shí)間:
#include <math .h>
#include <stdio .h>
#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.0-lat1)*(x);
b = (90.0-lat2)*(x);
theta = (lon2-lon1)*(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 -o ctest ctest.c編譯它,測(cè)試用time ./ctest ...大約0.2秒 。這使我有信心,我Cython擴(kuò)展相對(duì)于我的C代碼也極有效率(這并不是說我的C編程能力很弱)。
能夠用 cython 優(yōu)化多少性能通常取決于有多少循環(huán),數(shù)字運(yùn)算和Python函數(shù)調(diào)用,這些都會(huì)讓程序變慢。已經(jīng)有一些人報(bào)告說在某些案例上 100 至 1000 倍的速度提升。至于其他的任務(wù),可能不會(huì)那么有用。在瘋狂地用 Cython 重寫 Python 代碼之前,記住這一點(diǎn):
"我們應(yīng)該忘記小的效率,過早的優(yōu)化是一切罪惡的根源,有 97% 的案例如此。"——Donald Knuth
換句話說,先用 Python 編寫程序,然后看它是否能夠滿足需要。大多數(shù)情況下,它的性能已經(jīng)足夠好了……但有時(shí)候真的覺得慢了,那就使用分析器找到瓶頸函數(shù),然后用cython重寫,很快就能夠得到更高的性能。
外部鏈接
WorldMill(http://trac.gispython.org/projects/PCL/wiki/WorldMill)——由Sean Gillies 用 Cython 編寫的一個(gè)快速的,提供簡(jiǎn)潔的 python 接口的模塊,封裝了用以處理矢量地理空間數(shù)據(jù)的 libgdal 庫(kù)。
編寫更快的 Pyrex 代碼(http://www.sagemath.org:9001/WritingFastPyrexCode)——Pyrex,是 Cython 的前身,它們有類似的目標(biāo)和語(yǔ)法。
- Cython編譯python為so 代碼加密示例
- 用Cython加速Python到“起飛”(推薦)
- 如何用C代碼給Python寫擴(kuò)展庫(kù)(Cython)
- 詳解如何利用Cython為Python代碼加速
- Python實(shí)現(xiàn)常見的幾種加密算法(MD5,SHA-1,HMAC,DES/AES,RSA和ECC)
- python encrypt 實(shí)現(xiàn)AES加密的實(shí)例詳解
- python加密解密庫(kù)cryptography使用openSSL生成的密匙加密解密
- Python 實(shí)現(xiàn)加密過的PDF文件轉(zhuǎn)WORD格式
- 如何使用Cython對(duì)python代碼進(jìn)行加密
相關(guān)文章
Python機(jī)器學(xué)習(xí)之PCA降維算法詳解
今天帶大家復(fù)習(xí)python機(jī)器學(xué)習(xí)的知識(shí)點(diǎn),文中對(duì)PCA降維算法介紹的非常詳細(xì),對(duì)正在學(xué)習(xí)python機(jī)器學(xué)習(xí)的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05python 數(shù)字轉(zhuǎn)換為日期的三種實(shí)現(xiàn)方法
在Python中,我們經(jīng)常需要處理日期和時(shí)間,本文主要介紹了python 數(shù)字轉(zhuǎn)換為日期的三種實(shí)現(xiàn)方法,包含datetime模塊,strftime方法及pandas庫(kù),具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02Python 3.x對(duì).CSV數(shù)據(jù)按任意行、列讀取的過程
這篇文章主要介紹了Python 3.x對(duì).CSV數(shù)據(jù)按任意行、列讀取的過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-05-05Python?jieba庫(kù)文本處理詞性標(biāo)注和關(guān)鍵詞提取進(jìn)行文本情感分析
這篇文章主要為大家介紹了Python使用中文文本處理利器jieba庫(kù)中的詞性標(biāo)注和關(guān)鍵詞提取功能進(jìn)行文本情感分析實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12合并百度影音的離線數(shù)據(jù)( with python 2.3)
這篇文章主要介紹了合并百度影音的離線數(shù)據(jù)( with python 2.3)的相關(guān)資料2015-08-08Pytorch搭建簡(jiǎn)單的卷積神經(jīng)網(wǎng)絡(luò)(CNN)實(shí)現(xiàn)MNIST數(shù)據(jù)集分類任務(wù)
這篇文章主要介紹了Pytorch搭建簡(jiǎn)單的卷積神經(jīng)網(wǎng)絡(luò)(CNN)實(shí)現(xiàn)MNIST數(shù)據(jù)集分類任務(wù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03使用Pandas的ExcelWriter操作excel的方法
這篇文章主要介紹了使用Pandas的ExcelWriter操作excel的方法,ExcelWriter這個(gè)插件有個(gè)坑,就是已經(jīng)設(shè)置好的格式是無(wú)法更改的,因此,由pandas轉(zhuǎn)成excel的時(shí)候,必須將格式清除,尤其是表頭的格式需要大家多多注意,本文結(jié)合示例代碼講解的非常詳細(xì),需要的朋友參考下吧2023-11-11pygame學(xué)習(xí)筆記(2):畫點(diǎn)的三種方法和動(dòng)畫實(shí)例
這篇文章主要介紹了pygame學(xué)習(xí)筆記(2):畫點(diǎn)的三種方法和動(dòng)畫實(shí)例,本文講解了單個(gè)像素(畫點(diǎn))、連接多個(gè)點(diǎn)形成線、引用圖像、動(dòng)畫完整實(shí)例,需要的朋友可以參考下2015-04-04