什么锐朴,聽說3分鐘入門Cython?蔼囊?

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)這是由于某些組件的缺失造成的扮休,它們可能是下面這些組件:

  1. python-devel(對應(yīng)python2.x)/python3-devel(對應(yīng)python3.x)
  2. libxml2-dev
  3. 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》????????天才白癡書館
????知乎之提問????????知乎用戶 答

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渠啤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子添吗,更是在濱河造成了極大的恐慌埃篓,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件根资,死亡現(xiàn)場離奇詭異,居然都是意外死亡同窘,警方通過查閱死者的電腦和手機玄帕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來想邦,“玉大人裤纹,你說我怎么就攤上這事。” “怎么了鹰椒?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵锡移,是天一觀的道長。 經(jīng)常有香客問我漆际,道長淆珊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任奸汇,我火速辦了婚禮施符,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘擂找。我一直安慰自己戳吝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布贯涎。 她就那樣靜靜地躺著听哭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪塘雳。 梳的紋絲不亂的頭發(fā)上陆盘,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機與錄音粉捻,去河邊找鬼礁遣。 笑死,一個胖子當(dāng)著我的面吹牛肩刃,可吹牛的內(nèi)容都是我干的祟霍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼盈包,長吁一口氣:“原來是場噩夢啊……” “哼沸呐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呢燥,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤崭添,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叛氨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呼渣,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年寞埠,在試婚紗的時候發(fā)現(xiàn)自己被綠了屁置。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡仁连,死狀恐怖蓝角,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤使鹅,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布揪阶,位于F島的核電站,受9級特大地震影響患朱,放射性物質(zhì)發(fā)生泄漏鲁僚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一麦乞、第九天 我趴在偏房一處隱蔽的房頂上張望蕴茴。 院中可真熱鬧,春花似錦姐直、人聲如沸倦淀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撞叽。三九已至,卻和暖如春插龄,著一層夾襖步出監(jiān)牢的瞬間愿棋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工均牢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糠雨,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓徘跪,卻偏偏與公主長得像甘邀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子垮庐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容