我最喜欢的是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
)