1 前言
上一主题中,我们介绍了如何构建Cython代码,对于一般的python代码,都可以使用cython对其进行编译(当然也有列外,比如一些Cython尚未支持的语言特性https://cython.readthedocs.io/en/latest/src/userguide/limitations.html#cython-limitations),使动态的python代码变成静态的编译语言,从而提高运行效率(一般可以提升30%左右的效率)。然而在一些场景下(e.g.科学计算),你可能希望获得更快的效率,通过添加静态类型声明Cython也能为你办到!正如编译语言中的类型声明一样,Cython中的静态类型声明可以使得python代码脱离python的动态特性,生成更快的C代码,在一些情形下,能够使你的运算获得数十倍的效率提升!
2 变量类型声明
声明类型的方式如下:以cdef
作为开头,定义类型与变量名,基本上是c与python的结合吧。
# file: test.pyx
# 声明一个变量
cdef int x
cdef double s, dx
# 声明一堆变量
cdef:
int x
double y
double z
所有C的类型(int float double...)都可以在.pyx
文件中声明使用,cython会自动完成python与C之间的类型转换。需要特别指出的是python中的不定长整型在C中运行时,如果发生溢出,则会在运行时报OverflowError
的错误,但是在做数学运算时,并不会检查溢出,在这种情况下,生成的C代码将正确、安全地处理平台相关的C类型(这一块我目前还没有理解透彻,期待评论区大神的留言)。
看一个官方例子:
python程序:
# file: integrate_f.py
def f(x):
return x ** 2 - x
def integrate_f(a, b, N):
s = 0
dx = (b - a) / N
for i in range(N):
s += f(a + i * dx)
return s * dx
改写为cython:
# file: integrate_f_cy.pyx
def f(double x):
return x ** 2 - x
def integrate_f(double a, double b, int N):
cdef int i
cdef double s, dx
s = 0
dx = (b - a) / N
for i in range(N):
s += f(a + i * dx)
return s * dx
由于i
被声明为C类型,因此,该循环体会被转化为纯C代码执行。 a
, s
和 dx
参与了循环,因此对这三个变量进行类型声明将显著提升效率,而b
和N
则不会对效率产生深远影响。
为了验证一下,可以再在同一文件目录下新建一个python文件test.py
。分别导入上述两个文件,对比一下速度,记得integrate_f_cy.pyx
需要编译后才能import
成功。
3 函数类型声明
之前用def
定义的函数本质上还是python的函数,python的函数调用开销是相对较大的,因此在函数调用频繁的场合,可以直接将函数声明为C-style的风格:
cdef double f(double x) except? -2:
return x ** 2 - x
cdef
是声明函数的标记,可以像C一样指定返回值的类型为double
, except? -2
在这里的意思是:如果函数调用发生了异常,则返回-2
,这方便我们追踪bug。如果函数的返回类型是python对象或者你可以保证函数不会出现异常,except也可以不写。
还需要说明的是,cdef
声明的函数在编译后是不能在.py
文件中调用的,它更像是cython的一个内部函数,对外不可见。如果你想写一个可以在.py
中调用的函数,请用cpdf
作为函数的声明。
-
说了这么多,来个实验吧!看看类型声明能够带来多少效率提升。
同样的函数(从1加到10亿),python原生函数简直奇慢57.4s,简单编译不指定类型,运行结果为35.6s,指定参数类型后,运行时间竟然达到了0.3s!!!
python.png
截图20201130185059.png
截图20201130185656.png
4 后记
天下武功,唯快不破!但鱼与熊掌不可兼而得之。这意味着你想要获得更快的执行效率,你就不得不放弃一些python优美特性:可读性、动态性、高效开发;反之,如果你想要更快地开发你的产品,那么就不要过多地关注于静态类型这些底层维度的东西。客观地讲,Cython的目的不是为了让你全盘皆用Cython开发,如果你是狂热的运行效率追求者,那么就直接上C/C++,这样写出的代码更加稳定高效;如果不是,如无必要,就不要过度依赖Cython而坏了python的味道。
网友评论