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"
)
"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"
)
"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"
)
"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 黑魔法 ---上下文管理器(contextor)
※看我如何假裝是一個編程極客
TAG:Python開發 |