使用cython加速慢如蜗牛的纯python代码。
What’s Cython
官方解释:Cython is Python with C data types,翻译过来就是Cython是可以使用C数据类型的python。在实际使用中,Cython可以让你在纯python基础上,使用C语言的数据类型定义python的数据,然后将python
代码转成C代码,再将其编译为动态链接库,在其他python程序中可以像其他库一样直接import编译的Cython模块。
使用Cython的的优点是:
- 能极大的加快程序的运行速度
- 能使用绝大多数python的第三方库
- 方便使用C函数和C库
但是为了使用它,需要修改原始代码,虽然修改量不算很大。
基本使用方法
安装Cython
Cython和python的其他第三发库一样,既可以使用pip等工具自动安装即可,也可以在官方下载安装文件手动安装。如果是使用的Anconda,那其中已经包含Cython了。
必要文件准备
Cython的使用和普通的第三方库可以直接在代码中import后就可以使用不一样,它是于pyinstaller、swig这种工具类似,除了需要在代码需要修改,还需要一些额外的操作。首先就是需要有两个python文件:
- 从原始的python代码修改的加入了C数据类型申明的“.pyx”文件,这就是我们需要加速的python代码文件
- setup.py文件,用于确定转换的一些配置选项,并启动Cython转换
.pyx文件的写法
.pyx文件由原始的.py文件而来,不过原始python代码也可以不做任何修改只把后缀名从“.py”变为“.pyx”,但是这样无法提高效率了。将.py文件改写成.pyx文件的主要操作就是把原有的python动态类型在使用前,先用C语言的数据类型加以声明,声明时使用“cdef”作为前缀关键字
如使用整型变量和整型数组时先声明如下:
1 | cdef int n, i, len_p |
在使用numpy的代码中,需要注意numpy的类型声明,下面为一个比较完整的函数改写,原始代码如下:
1 | import numpy as np |
改写后为:
1 | import numpy as np |
上面要注意如下几点:
- 使用cimport导入numpy和cython
- 导入cython时为了调用其中的boundscheck和wraparound方法关闭边界检查,这个可选。
- 有返回值的函数定义没有了def,为了保证外部能使用,需要写个接口函数
- 函数的参数不需要“cdef”的前缀
- numpy数组的声明需要确定形状和元素类型
setup.py文件写法
最简单的setup.py文件内容如下(仅仅指定了需要转换的.pyx文件):
1 | from distutils.core import setup |
稍微复杂的(加了numpy使用的):
1 | from distutils.core import setup, Extension |
具体写法需要参考官方文档。
转换流程
在命令行中执行如下命令,就可完成转化和编译:
1 | python setup.py build_ext --inplace |
完成后,会生成两个文件:.c文件和库文件(Windows后缀为.pyd,Linux后缀为.so)。在Windows中生成的库文件不要修改文件名,不然会无法使用。在使用时直接在python代码中欧个import库名就可以了。
在jupyter notebook中使用
在jupyter notebook中使用Cython会简单很多,因为jupyter notebook自动帮你执行的转化的操作,但是原始代码还是需要参照Cython的要求来修改。
使能Cython
在jupyter notebook中,使用单独的一个cell,写入:
1 | %load_ext Cython |
使用cython编译
在一个cell的最前面,写入:
1 | %%cython |
那个当前的cell中的代码就会使用cython来编译
使用不同cell中Cython数据对象
需要注意的是,在jupyter notebook中每个cell是独立的,在使用Cython的不同cell中,要用到其他Cython编译的cell中的数据对象,需要import:
1 | from __main__ import xxx |
注意点
- numpy数据引用,用[,]比[][]效率要高,普通的使用中对比不是特别明显,但是在cython编译循环语句中使用时差距就会变得非常大,甚至相差百倍: 对比二维数组赋值一个0.1s,一个0.002s
- 在cython中,有定义类型的数据和没有定义类型的数据做运算的时候,会严重拖慢速度完全起不到提速的效果,一定要对每个有参与计算的类型对象声明类型
- 在符合规范的情况下,cython编译的结果比纯python(不包含优化的第三方库)要快400多倍
- 一般n,m,k = img.shape 需要写成 n,m,k = img.shape[0], img.shape[1], img.shape[2] 才能编译成功
- numpy赋值时new_img = np.zeros((rgb_img.shape[0],rgb_img.shape[1],rgb_img.shape[2]), dtype=np.uint16)+256,拆成new_img = np.zeros((rgb_img.shape[0],rgb_img.shape[1],rgb_img.shape[2]),dtype=np.uint16)和new_img += 256两部分效率要高
- cython中使用numpy的数组的并行操作效率极差,要用for循环代替