當前位置:
首頁 > 知識 > Cython 三分鐘入門

Cython 三分鐘入門

點擊上方「

Python開發

」,選擇「置頂公眾號」


關鍵時刻,第一時間送達!






我最喜歡的是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.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




讓我們調用它 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.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




請注意,我們仍然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

.

5

/

c1

.

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.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




與 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.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




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




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





#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 -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



  • Python開發整理髮布,轉載請聯繫作者獲得授權


【點擊成為Java大神】

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發 的精彩文章:

python 黑魔法 ---上下文管理器(contextor)
看我如何假裝是一個編程極客

TAG:Python開發 |