文章目录
- 1. 什么是 Cython?
- 2. 用 Cython 编写1个函数
- 1) 安装 cython
- 2) 先编写1个纯python函数
- 3)使用cython重写该函数
- 4) 编译 .pyx 文件
- 3. 运行cython 函数
- 1) 导入 cython 模块
- 2) 运行cython 函数
- 4. 与纯python函数进行性能比较
- 1) 运行纯python函数
- 2) 比较结果
- 5. 总结
听过很多人说python速度慢,难以用于正式项目。本文介绍运用Cython编程,令Python代码的运行速度能提升数倍至数百倍。
1. 什么是 Cython?
Cython是Python编程语言的一个超集,它在Python和C/C++之间起着中间人的作用。Cython 允许python 与 C 风格代码混合编程,将C语句代码编译为静态格式,Python解释器运行时动态调用。当然,这意味着Cython永远不可能比C/C++快,稍微会慢一点。
目前,有许多知名的Python库,如NumPy和Pandas已经使用Cython来提高性能。也说明学习它很值得。下面我将演示如何用cython来改写1个python函数,并编译为二进制文件,测试比较前后性能变化。
2. 用 Cython 编写1个函数
1) 安装 cython
建议使用Python3.9 以上版本
pip3 install cython
2) 先编写1个纯python函数
我们编写1个函数,求圆周率近似值,数学公式如下
下面代码使用纯python实现此计算,默认n = 10000000, 并导入cProfile 模块来计算各函数消耗时间。
# 文件名: pi.py
import cProfile
def recip_square(i):
return 1. / i ** 2
def approx_pi(n=10000000):
val = 0.
for k in range(1, n + 1):
val += recip_square(k)
pi = (6 * val) ** .5
print("Approximate value of pi is: ", pi)
return pi
if __name__ == '__main__':
# 计算PI, 并统计耗时
cProfile.run('approx_pi(10000000)')
3)使用cython重写该函数
cython 编程就是在python中使用C类型来申明变量,要先申明再使用。允许导入 C++原码。cython 代码的后缀名为 .pyx.
用cython语法来编写计算圆周率的函数,保存在cpi.pyx文件中。
# 文件名 cpi.pyx
# cython: profile=True
cimport cython
@cython.profile(False)
cdef inline double recip_square(long long i):
return 1.0 / (i * i)
def approx_pi(int n=10000000):
cdef double val = 0.
cdef int k
for k in range(1, n + 1):
val += recip_square(k)
pi = (6 * val) ** .5
print("Approximate value of pi is: ", pi)
return pi
下面分析这段代码:
cdef double val = 0.
cdef int k
其中 double, int 是 C 语言浮点数、整数类型, cdef 表示使用静态编译
cdef inline double recip_square(long long i)
函数定义时,也用C类型申明参数类型,以及返回值类型.
def approx_pi(int n=10000000):
approx_pi函数名前面无 cdef ,表示这个函数不是纯C函数,在运行时会按python方式运行,解释器执行到 cdef double val = 0.
语句时,会从编译后的pyd动态链接库中调用C语句。
因为循环体内用到的变量与函数都是C语句,所以它的速度是非常快的。 函数的返回值 pi 变量是1个python变量。 但只调用了2次,因此对总体速度影响非常有限。
4) 编译 .pyx 文件
Cython 使用Python最通用的 setuptools 构建工具进行编译,但必须提前安装好C++编译器,linux上安装gcc, Windows 系统安装Visual Studio 或者minGW均可。
第1步,按setuptools 要求,编写setup.py构建脚本,setup.py构建的详细配置不赘述了,仅说明与cython相关的配置
# setup.py
from setuptools import setup
# 导入Cython 构建相关模块
from Cython.Build import cythonize
setup(
ext_modules=cythonize("cpi.pyx"),
)
其中 ext_modules=cythonize("cpi.pyx")
,告知setuptools ,将cpi.pyx 按c来编译。
第2步,执行编译
python setup.py build_ext --inplace
会先转为 cpi.c 文件,再用C++编译器将 .c文件编译为动态链接库.pyd, linux则为.so。
3. 运行cython 函数
1) 导入 cython 模块
编写1个python测试函数,导入编译后的cython模块, 用 cProfile 模块收集运行时间
# test_cpi.py
import cpi
import cProfile
cProfile.run('cpi.approx_pi()')
2) 运行cython 函数
执行该函数,总耗时 0.042秒
python test_cpi.py
Approximate value of pi is: 3.1415925580959025
5 function calls in 0.042 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.042 0.042 <string>:1(<module>)
1 0.042 0.042 0.042 0.042 cpi.pyx:15(approx_pi)
1 0.000 0.000 0.042 0.042 {built-in method builtins.exec}
1 0.000 0.000 0.042 0.042 {cython_demo.cpi.approx_pi}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
4. 与纯python函数进行性能比较
1) 运行纯python函数
执行python函数,总耗时 6.287秒,
python pi.py
Approximate value of pi is: 3.1415925580959025
10000005 function calls in 6.287 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 6.287 6.287 <string>:1(<module>)
10000000 3.944 0.000 3.944 0.000 pi.py:2(recip_square)
1 2.343 2.343 6.287 6.287 pi.py:5(approx_pi)
1 0.000 0.000 6.287 6.287 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
2) 比较结果
结果相比较, 6.287 / 0.042 = 150 倍
这个计算圆周率的函数,用cython语法简单改写后,性能提升了150倍。
5. 总结
从示例可以看出,密集运算的函数经过cython简单添加了C类型申明后,并没有增加更多代码,但速度提升非常明显,可以说,Cython是提升Python程序性能的最佳方式,因此值得深入学习,并在实际项目上运用。
使用cython时的一些建议,供参考:
1) cython语法相比python基本语法,学习难度略微增加,建议理解原理后,多做几遍练习。
2) 只在少数代码中使用cython, 主要是运算很重的函数或类。
3)在I/O密集型函数中使用cytthon效果不明显,或者可能没什么效果,如网络消息收发,大文件读写。