问题
假设一个计算密集型任务,计算的函数是形如:
计算N遍,N很大,那么在python中有多种实现方式最简单的两种方式:
1、基于numpy库
import numpy as np
def original_complex_calc(x, y):
z = np.sqrt(x) * np.log(y) * np.power(x, y)
return z
2、基于math库
import math
def original_complex_calc_math(x, y):
z = math.sqrt(x) * math.log(y) * math.pow(x, y)
return z
测试一下两者都循环运行1000w次的性能:
original_complex_calc 26.025455951690674
original_complex_calc_math 5.273261070251465
可以看到math库基础运算性能要比numpy要好,那有没有方法再提升一下性能呢,用cython掉c的库
cython
python容易调用c/c++的代码,使用cython可以有两个好处:
- 首先可以提升python性能
- 可以条用c/c++的库,比如opencv,做图像处理基本上都会用到
1、首先需要写一个pyx文件,内部完成调用c库逻辑和计算逻辑
例如:calculation.pyx
# @Time : 2020/7/7
# @Author : 大太阳小白
# @Software: PyCharm
# @blog:https://blog.csdn.net/weixin_41579863
cdef extern from "math.h" nogil:
double sqrt(double)
double pow(double, double)
double log(double)
cdef double _complex_calc(double a, double b):
cdef double c = sqrt(a) * log(b) * pow(a,b)
return c
def complex_calc(a, b):
return _complex_calc(a, b)
- Cython 程序的扩展名是 .pyx
- Cython 的函数使用 cdef 定义,并且他可以给所有参数以及返回值指定类型。
- 在 Python 程序中,是看不到 cdef 的函数的,需要借助complex_calc调用cdef函数
2、其次写一个setup.py文件对calculation.pyx进行编译生成动态链接库,setup.py内容如下:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("calculation_cython", ["calculation.pyx"])]
setup(
name='calculation app',
language='c',
cmdclass={'build_ext': build_ext},
ext_modules=ext_modules,
)
- Cython 编译器把 Cython 代码编译成调用了 Python 源码的 C/C++ 代码
- 把生成的代码编译成动态链接库
- Python 解释器载入动态链接库
使用命令生成链接库:
python setup.py build_ext --inplace
linux系统上生成.so文件,windows系统上生成.pyd文件
3.直接使用import调用编译好的动态链接库
import calculation_cython
if __name__ == '__main__':
a = 10.0
b = 10
print(calculation_cython.complex_calc(a, b))
最终性能测试一下
# @Time : 2020/7/7
# @Author : 大太阳小白
# @Software: PyCharm
# @blog:https://blog.csdn.net/weixin_41579863
import numpy as np
import calculation_cython
import time
import math
def original_complex_calc(x, y):
z = np.sqrt(x) * np.log(y) * np.power(x, y)
return z
def original_complex_calc_math(x, y):
z = math.sqrt(x) * math.log(y) * math.pow(x, y)
return z
if __name__ == '__main__':
a = 10.0 # 设置成浮点防止numpy的power出现溢出错误
b = 10
print(original_complex_calc_math(a, b))
print(original_complex_calc(a, b))
print(calculation_cython.complex_calc(a, b))
test_funs = [original_complex_calc, original_complex_calc_math, calculation_cython.complex_calc]
for func in test_funs:
start = time.time()
for _ in range(1000 * 10000):
func(a, b)
print(func.__name__, time.time() - start)
结果:
72814134002.11803
72814134002.11803
72814134002.11803
original_complex_calc 26.025455951690674
original_complex_calc_math 5.273261070251465
complex_calc 1.5478901863098145
性能再次提升!