Cython是一个快速生成Python扩展模块的工具,从语法层面上来说是Python语法和C语言语法的混血,当Python性能遇到瓶颈时,Cython直接将C的原生速度植入Python程序,这样使Python程序无需使用C重写,能快速整合原有的Python程序,这样使得开发效率和执行效率都有很大的提升,而这些中间的部分,都是Cython帮咱们作了,接下来简单说一下Cython的安装和使用方法python
1、首先Cython官网地址是:http://cython.org/ 这里有cython的安装和开发文档,关于Cython的下载能够在pypi上直接下载安装包:https://pypi.python.org/pypi/Cython/ 因为是在Linux下安装使用,这里下载的是Cython-0.25.2.tar.gz,上传至linux执行以下步骤安装:linux
tar -xvzf Cython-0.25.2.tar.gz cd Cython-0.25.2 python setup.py install
这样Cython模块就安装成功了多线程
而后咱们首先看一下cython的基本使用,首先 mkdir hello_pack && cd hello_pack 创建一个目录而且进入,而后编写一个hello.pyx的脚本,代码以下:函数
# coding=utf-8 def print_hello(name): print "Hello %s!" % name
代码很简单,就是一个函数,而后编写一个setup.py,代码以下:工具
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("hello",["hello.pyx"])] setup( name = "Hello pyx", cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules )
这里导入了Cython的模块,其中setup name指定模块的名称,而后执行编译命令:性能
python setup.py build_ext --inplace
编译完以后,看到当前目录下会生成两个文件,一个是hello.c一个是hello.so,hello.c就是转换而成的c代码,而hello.so就是咱们须要的python通过Cython编译以后的模块,咱们为了当前目录可被调用,创建__init__.py内容以下:测试
# coding=utf-8 from hello import *
而后执行 cd .. 回到上层目录,创建一个hello.py,代码以下:优化
#!/usr/bin/python # coding=utf-8 from hello_pack import hello hello.print_hello("cython")
很简单,就是调用咱们编译好的hello模块,而后执行里面的方法,如今直接执行hello.py,看到输出结果正常ui
那么如今,咱们就完成了Cython的基本使用,其实setup.py编译脚本也能够写成以下这样:spa
from distutils.core import setup from Cython.Build import cythonize setup( name='Hello pyx', ext_modules=cythonize('hello.pyx') )
这种写法更通用,编译的时候直接使用 python setup.py build 就能够了,注意不是install,执行install后会把模块复制到python系统目录下了;
执行完以后,会看到当前目录有一个build目录,而后进去会发现有以下两个目录:
第二个temp是编译过程当中汇编生成的.o文件,第一个lib开头的目录中存放的就是hello.so文件,直接把这个文件拷贝出来就可使用了,另外为了方便,运行脚本直接和so文件放在一个目录下就能够,直接使用import hello便可导入,也不用创建__init__.py了
2、以上就是基本的Cython使用,下面借助一个案例来讲明Cython和Python程序有哪些性能方面的提高
首先咱们编写一个compute.py,封装了两个计算的方法,代码以下:
# coding=utf-8 import math def spherical_distance(lon1, lat1, lon2, lat2): radius = 3956 x = math.pi/180.0 a = (90.0 - lat1)*x b = (90.0 - lat2)*x theta = (lon2 - lon1)*x distance = math.acos(math.cos(a)*math.cos(b)) + (math.sin(a) * math.sin(b) * math.cos(theta)) return radius * distance def f_compute(a, x, N): s = 0 dx = (x - a)/N for i in range(N): s += ((a + i * dx) ** 2 - (a + i * dx)) return s * dx
第一个方法能够计算地球表面任意两个经纬度之间的距离,这个方法使用了不少三角函数,这些三角函数由python的math模块提供;第二个方法就是纯数字的计算
那么如今编写一个执行脚原本调用函数,test.py代码以下:
#!/usr/bin/env python # coding=utf-8 import compute import time lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826 start_time = time.clock() compute.f_compute(3.2, 6.9, 1000000) end_time = time.clock() print "runing1 time: %f s" % (end_time - start_time) start_time = time.clock() for i in range(1000000): compute.spherical_distance(lon1, lat1, lon2, lat2) end_time = time.clock() print "runing2 time: %f s" % (end_time - start_time)
代码就是分别执行100000次数字计算和距离计算,并打印之间,执行结果以下:
能够看到数字计算耗时0.33s,距离计算耗时1.34s,而后咱们尝试直接用Cython模块进行编译
首先复制出来一份 cp compute.py compute1.pyx 而后编写setup1.py
from distutils.core import setup from Cython.Build import cythonize setup( name='compute_module', ext_modules=cythonize('compute1.pyx'), )
写完以后直接执行 python setup1.py build 编译,编译完以后导入compute1.so这个模块,如今只须要修改头部import compute为import compute1 as compute这样能保证下面调用代码不变,而后执行,结果以下:
如今会发现时间比刚才快了一点,说明性能有所提高,而后继续进行优化,此次所有变量都使用静态类型,即变量类型提早定义,compute2.pyx代码以下:
# coding=utf-8 import math cpdef float spherical_distance(float lon1, float lat1, float lon2, float lat2): cdef float radius = 3956 cdef float pi = 3.14159265 cdef float x = pi/180.0 cdef float a,b,theta,distance a = (90.0 - lat1)*x b = (90.0 - lat2)*x theta = (lon2 - lon1)*x distance = math.acos(math.cos(a)*math.cos(b)) + (math.sin(a) * math.sin(b) * math.cos(theta)) return radius * distance def f_compute(double a, double x, int N): cdef int i cdef double s = 0 cdef double dx = (x - a)/N for i in range(N): s += ((a + i * dx) ** 2 - (a + i * dx)) return s * dx
如今能够看到类型所有作了定义,在cython里面,类型定义使用cdef float这种方式进行;对于方法来讲,若是模块内部相互调用那么一样使用cdef double这种的方式来定义,若是这个方法要在咱们外部执行脚本中调用,那么要么是python原生方法不作任何修改,要么写成cpdef float这种类型的形式,方法定义类型能够提升效率,可是提升不大,可是变量静态类型能够极大的提升效率,缘由是参与计算的主要是变量;假如一个函数被频繁调用,那么有必要使用cdef或者cpdef来定义;如今一样方法编译compute2模块,而后在test.py中调用,执行结果以下:
如今发现纯数字计算的时间几乎变成了0秒!而第二个用了0.81s,比刚才快了,可是彷佛并非很快,缘由仔细想一想会发现,这里面调用了不少三角函数,使用的仍是python内置的math模块,这里能够用C语言的math.h来代替,因此再写一个compute3.pyx以下:
# coding=utf-8 #import math cdef extern from "math.h": float cosf(float theta) float sinf(float theta) float acosf(float theta) def spherical_distance(float lon1, float lat1, float lon2, float lat2): cdef float radius = 3956 cdef float pi = 3.14159265 cdef float x = pi/180.0 cdef float a,b,theta,distance a = (90.0 - lat1)*x b = (90.0 - lat2)*x theta = (lon2 - lon1)*x distance = acosf(cosf(a)*cosf(b)) + (sinf(a) * sinf(b) * cosf(theta)) return radius * distance def f_compute(double a, double x, int N): cdef int i cdef double s = 0 cdef double dx = (x - a)/N for i in range(N): s += ((a + i * dx) ** 2 - (a + i * dx)) return s * dx
此次咱们屏蔽了自身的math模块,而后调用了C的头文件math.h,并引入了cosf,sinf,acosf,实际上这三个函数返回的都是float类型,和咱们定义的一致,若是咱们想使用double类型,那么应该直接使用cos,sin,acos函数,而且咱们能够对这些函数再包装,这样cpdef就派上用场了,好比下面:
cdef extern from "math.h": double cos(double) double sin(double) cpdef double tangent(double x): return sin(x)/cos(x)
好比这里定义了一个测试的tangent正切函数,就是上面这样写;一样咱们如今编译compute3.pyx并测试,结果以下:
如今第一个仍然是接近于0s完成,而第二个三角函数换成C的以后,性能有的很是大的提高,能够说几乎接近于原生C语言的效率了,这样看来从Python到Cython性能获得了很大的优化,可是代码量确差异没有用C重写那么大
根据上面的案例,咱们知道在数字,浮点数等计算中Cython能够极大的提升性能,而这方面多线程几乎不能提升任何性能,有时候反而会下降;可是对于io密集型的场合,用Cython基本上也没有性能上太大的提高,而多线程的将拥有更加出色的性能,因此Cython应该专一与计算方面的优化;总结一下也就是对于IO密集型应用,优化能够考虑使用多线程或者多进程方式,对于计算密集型的场合,遇到瓶颈时能够考虑使用Cython或者封装C相关的模块
最后,虽然Cython很是好用,但也不能疯狂的使用,推荐一句名言:咱们应该忘记小的效率,过早的优化是一切罪恶的根源,有 97% 的案例如此。简单来讲就是选择恰当的时机进行优化