Python的代碼優(yōu)雅而實用焚志,但是它的速度確實不怎樣。Cython企圖在保留Python美好的同時畏鼓,兼顧代碼的效率酱酬,這里先貼出Cython的兩個官方文檔,以方便大家對Cython更系統(tǒng)的學(xué)習(xí):
英文 http://docs.cython.org/en/latest/
中文 https://moonlet.gitbooks.io/cython-document-zh_cn/content/ch1-basic_tutorial.html
為什么最近會和Cython打上交道云矫,說起來是因為我看了一篇名叫《Cython三分鐘入門》的文章膳沽,毫無疑問文章寫得很不錯,但是標題起得實在不好让禀,因為太標題黨啦挑社!
跑通整個項目我花了大概兩天時間,盡管代碼量并不大巡揍。接下來我仍然會以文章中的例子來進行說明痛阻,結(jié)合這兩天的經(jīng)歷,從三個環(huán)境出發(fā):1. Windows環(huán)境腮敌;2. Linux環(huán)境阱当;3. Cgywin環(huán)境俏扩,將我遇到的問題一并講清楚,希望能幫助到和我有相同問題的同學(xué)弊添。
Cython的安裝
Cython是一個Python庫录淡,理所當(dāng)然的安裝手段就是
-> pip intsall cython
當(dāng)然對于Cygwin用戶,也能夠進入Cygwin安裝程序中下載Cython包表箭,效果是一樣的赁咙,步驟可以參考我的另一篇文章《Cygwin,讓你擁有Windows下的Linux環(huán)境》免钻。
代碼效率
由于各主機的配置不一樣彼水,你測試出來的時間和我不一樣很正常,但是這種性能的上升是必定和我一樣的极舔!
Python版本
下面是一段Python實現(xiàn)的代碼凤覆,作用是計算沿地球表面兩點之間的距離,是我們的version1拆魏。我們會讓它調(diào)用50萬次盯桦,并測定它所需的時間:
# version1.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
實現(xiàn)以下代碼來調(diào)用version1::
# efficiency.py
import timeit
lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
num=500000 #調(diào)用50萬次
t=timeit.Timer("v1.great_circle(%f,%f,%f,%f)"%(lon1,lat1,lon2,lat2),
"import version1 as v1")
print('純python版本用時:'+str(t.timeit(num))+'sec')
好的,我的電腦花了大約3.3s渤刃,有點感人拥峦。當(dāng)然我們的時間可能并不一樣,但無一例外都不怎么快卖子。
Python+C版本
一定程度上混搭Python和C數(shù)據(jù)類型略号,是我們的version2,但要注意洋闽,必須是.pyx文件玄柠,因為我們需要將其編譯為Python拓展,這一步我會在后面仔細講诫舅。我們同樣會調(diào)用50萬次羽利,并測定它所需的時間:
# version2.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 c*radius
實現(xiàn)以下代碼來調(diào)用version2:
# efficiency.py
import timeit
lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
num=500000 #調(diào)用50萬次
t=timeit.Timer("v2.great_circle(%f,%f,%f,%f)"%(lon1,lat1,lon2,lat2),
"import version2 as v2")
print('python+c版本用時:'+str(t.timeit(num))+'sec')
我的電腦花了大約2.2s,與3.3s相比是不是快了不少刊懈。相信在你的電腦上也能夠看到這么明顯的變化这弧。
C版本(Python調(diào)用)
我們大致分析一下,version2的瓶頸是在哪里俏讹!我們調(diào)用的是python的math模塊当宴,是不是這里大大地限制了性能?現(xiàn)在我們使用C標準庫替代之:
# version3.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-lat1)/(x)
b=(90-lat2)/(x)
theta=(lon2-lon1)*(x)
c=acosf((cosf(a)*cosf(b))+(sinf(a)*sinf(b)*cosf(theta)))
return radius*c
實現(xiàn)以下代碼來調(diào)用version3:
# efficiency.py
import timeit
lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
num=500000 #調(diào)用50萬次
t=timeit.Timer("v3.great_circle(%f,%f,%f,%f)"%(lon1,lat1,lon2,lat2),
"import version3 as v3")
print('純c版本(Python函數(shù)調(diào)用)用時:'+str(t.timeit(num))+'sec')
0.6s泽疆,相當(dāng)驚人;浮!這才是我們追求的速度不是嗎殉疼?
C版本(C調(diào)用)
觀察上面的代碼梯浪,容易發(fā)現(xiàn)調(diào)用50萬次這個是循環(huán)使用Python實現(xiàn)的捌年。我們知道循環(huán)是一個相當(dāng)耗時的操作,那么如果我們把這個循環(huán)放到C代碼里挂洛,是否能更進一步地提升性能:
# version4.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-lat1)*(x)
b=(90-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
實現(xiàn)以下代碼來調(diào)用version4:
# efficiency.py
import timeit
lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
t=timeit.Timer("v4.great_circle(%f,%f,%f,%f,%i)"%(lon1,lat1,lon2,lat2,num),
"import version4 as v4")
print('純c版本(C函數(shù)調(diào)用)用時:'+str(t.timeit(1))+'sec')
驚喜地發(fā)現(xiàn)礼预,我們把性能提升到了0.12s,速度提高了將近30倍虏劲,very amazing托酸。
C代碼實現(xiàn)
究竟Cython中最快的版本version4和C實現(xiàn)的代碼,在性能上相比會有多大的差距柒巫?我這里準備了一段C代碼:
#include <math.h>
#include <stdio.h>
#include <time.h>
#define NUM 500000
//version5.c
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;
clock_t start, finish;
double Total_time;
start = clock();
for(i=0;i<=NUM;i++)
x=great_circle(-72.345,34.323,-61.823,54.826);
finish = clock();
Total_time = (double)(finish-start) / CLOCKS_PER_SEC;
printf("%f sec",Total_time);
printf("\n");
printf("%f",x);
}
當(dāng)然励堡,如果你有C語言的集成環(huán)境,直接運行就能可以得到結(jié)果堡掏!我們知道Linux系統(tǒng)的gcc組件能夠編譯C代碼应结,為了方便,我們直接在Linux系統(tǒng)下編譯運行泉唁,這里我提供兩種方式:
方式一:
->Linux環(huán)境下:gcc -lm -octest version5.c
???當(dāng)前路徑生成ctest.exe
->time ./ctest
???測試該模塊運行所需時間
對于使用Cygwin來完成這條命令的用戶鹅龄,可能會遇到下面的麻煩,博主也遇到了
<center><font color="red">錯誤:</font>pyconfig.h No such file or directory
</center>
通過網(wǎng)上查閱相關(guān)資料亭畜,發(fā)現(xiàn)這是由于某些組件的缺失造成的扮休,它們可能是下面這些組件:
- python-devel(對應(yīng)python2.x)/python3-devel(對應(yīng)python3.x)
- libxml2-dev
- libxslt-dev
一般來說,主要是python-devel的原因拴鸵「嘏冢總之,看情況吧宝踪。
方式二:
->Linux環(huán)境下:gcc -o version5 version5.c
???當(dāng)前路徑生成version5.exe
->./version5
???測試該模塊運行所需時間
可能由于測試的方式不同,這里用時反而還要長那么一點碍扔。但總的來說瘩燥,Cython能夠有效地改善性能。當(dāng)然不同,大多數(shù)情況下厉膀,Python的性能是足夠好的,一旦循環(huán)二拐、數(shù)字運算和Python函數(shù)調(diào)用上去了服鹅,性能就會相應(yīng)地下降,在這種情況下百新,我建議你們使用Cython進行優(yōu)化企软。
Cython編譯
我們之前安裝的Cython就是用在這里的。
- 對于Windows系統(tǒng)饭望,編譯以下代碼能夠得到:.pyx->.pyd
- 對于Linux系統(tǒng)仗哨,編譯一下代碼能夠得到:.pyx->.c->.o->.dll
這是對應(yīng)的形庭,因為Windows系統(tǒng)能夠使用的Python拓展是.pyd文件,而Linux系統(tǒng)能夠使用的Python拓展是.o或.dll文件厌漂。但是我們完全不用擔(dān)心萨醒,你的系統(tǒng)總是有選擇地去做適合它自己的事情,我們只需要順水推舟苇倡。
#setup.py
# Run as:
# python setup.py build 編譯
# python setup.py install 安裝(效果同pip install xxx)
from distutils.core import setup
from Cython.Build import cythonize
#cythonize:編譯源代碼為C或C++富纸,返回一個distutils Extension對象列表
setup(ext_modules=cythonize('XXXXX.pyx'))
我們在前文提到的.pyx文件并不能直接作為Python拓展,我們需要編寫setup.py來幫助我們獲得Python拓展旨椒。在'XXXXX.pyx'中填入.pyx文件的路徑晓褪,然后我們在shell環(huán)境下(setup.py的目錄下)執(zhí)行以下命令:
python setup.py build
一般來說,我們能在當(dāng)前目錄下看到一個名為build的文件夾钩乍,我們需要的Python拓展就在里面辞州!注意執(zhí)行該命令的python版本要對應(yīng)。
執(zhí)行以下命令:
python setup.py install
安裝該模塊到site-package文件夾下寥粹。
Cython編譯中的問題
我之所以無法像文章所說的那樣三分鐘入門变过,就是因為這些亂七八糟的問題,這里我匯總一下我自己遇到的問題涝涤,希望后來的人不要像我一樣走彎路媚狰。原生Linux環(huán)境問題不大,會出現(xiàn)問題主要就是Windows系統(tǒng)和Cygwin這種偽Linux環(huán)境阔拳,我就從這兩個環(huán)境出發(fā):
Windows
Unable to find vcvarsall.bat
####
這時候的我們崭孤,一般直接就把這個錯誤貼到百度或者google上,那么我們會看到一堆關(guān)于Windows下Cython安裝的教程糊肠。
注意辨宠,不要按網(wǎng)上說的,安裝MinGW货裹,然后在"..python安裝路徑...\Lib\distutils"下新建一個distutils.cfg文件嗤形,在這文件里面制訂編譯器為mingw32
如:
[build]
compiler=mingw32
一方面,我的電腦上已經(jīng)安裝了Cygwin弧圆,為什么非得另外安裝一個MinGW赋兵,另一方面MinGW編譯出來的東西,安裝上了也有不好使的時候搔预,甚至?xí)o法編譯霹期;即使編譯通過,安裝上了拯田,你安裝的Python標準庫不是由mingw編譯的历造,你的拓展包卻是mingw編譯的,很難說能夠完全兼容或者質(zhì)量跟得上。(引用自參考文獻2)
但是參考文獻2中提到的方法卻不是很管用帕膜,可能不是很適合我的情況吧枣氧。那么要如何解決這個問題?這主要是因為涉及到了比較底層的問題垮刹,由于底層上對python支持的不足造成的达吞。參考文獻3提出的方法完美地解決了我的問題:
打開Visual Studio的安裝程序進入到下面的界面,選擇下面這些組件安裝荒典。
等待安裝完成酪劫,再次執(zhí)行命令發(fā)現(xiàn)能夠成功編譯!大成功~~
Cygwin
打開Cygwin寺董,執(zhí)行以下命令
python setup.py build
黑色的界面上彈出刺眼的紅色覆糟,提示你出錯了
致命錯誤:Python.h:沒有那個文件或目錄
####
其實說白了,Cygwin下報錯遮咖,大多數(shù)情況就是想告訴你滩字,你丫的有哪些哪些組件沒下載呀呀!御吞!
是的麦箍,只要我們安裝python-devel(python2.x)/python3-devel(python3.x),便發(fā)現(xiàn)問題迎刃而解啦陶珠!
這幾天收獲很大挟裂,巨開心!
項目鏈接: https://github.com/kingboung/Miniproject/tree/master/Cython_accidence
轉(zhuǎn)載請告知!揍诽!博主個人網(wǎng)站:http://www.kingboung.me
文章有不完善的地方诀蓉,請留言告知!謝謝我的朋友們暑脆。
參考文獻:
《Cython三分鐘入門》????????賴勇浩 譯
《徹底解決 error: Unable to find vcvarsall.bat》????????天才白癡書館
????知乎之提問????????知乎用戶 答