因为常常使用Python,经常能感觉到和C,Java来说Python的速度太慢了。其中很大程度上是因为Python的类型是动态的,在解释类型方面花了较长时间。在调研过程中,发现给Python提速一个比较可行的方案是用Cython改写。
Cython的原理,短板,和Cpython的比较,这篇知乎写的非常详细
Python是不是被严重高估了? - 铁甲万能狗的回答 - 知乎
https://www.zhihu.com/question/266096929/answer/2383570933
首先是Cython的安装,pip install Cython
然后还需要下载Visual Stdio编译C的软件工具,我在Windows上的下载链接如下
https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/
需要勾选两个工具,SDK和MSVC,缺少的话会提示找不到io.h文件
C:\ProgramData\Anaconda3\include\pyconfig.h(59): fatal error C1083: 无法打开包括文件: “io.h”: No such file or directory
error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Tools\\MSVC\\14.37.32822\\bin\\HostX86\\x64\\cl.exe' failed with exit code 2
Cython官方文档:
http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html#the-basics-of-cython
中文版文档:
https://www.kancloud.cn/apachecn/cython-doc-zh/1944774
一些注意事项:
https://www.codenong.com/dc7854776e4cf62deb76/
现有一段测试性能的Python运行代码如下:
def integrate_f(a, b, N):
s = 0
dx = (b-a) /N
for i in range(N):
s += 2.71828182846**(-(a+i*dx)**2)
return s*dx
def test_func():
return integrate_f(1, 10, 100000000)
Java版本
package org.example;
import java.lang.Math;
public class Test {
public static double integrate_f(double a, double b, int N) {
double s = 0;
double dx = (b-a)/N;
for(int i =0; i <N; i++) {
s += Math.pow(2.71828182846, -Math.pow(a+i*dx, 2));
}
return s*dx;
}
public static void main(String args[]) {
long stime = System.nanoTime();
double result = integrate_f(1,10, 100000000);
long etime = System.nanoTime();
System.out.println(result);
System.out.printf("执行时长:%d 纳秒.", etime - stime );
}
}
则它的Cython改写版本为:
(test_cython.pyx)
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
cdef integrate_f(double a, double b, int N):
cdef double s = 0
cdef int i
cdef double dx = (b-a) /N
for i in range(N):
s += 2.71828182846**(-(a+i*dx)**2)
return s*dx
def test_func():
return integrate_f(1, 10, 100000000)
可以看到比较典型的是多了两个注解,还有就是变量前通过cdef申明了类型
这样的Cython代码是不能直接在Pycharm里运行的,会提示invalid syntax。
需要进行编译后才能作为C扩展模块才能加入Python运行,这部分可以参考之前提到的官方文档,需要写一个类似于打包的setup程序。这里暂时使用的是一个简单的版本,name就是打包编译后的模块名,ext_modules就是需要进行编译的文件
(setup.pyx)
from setuptools import setup
from Cython.Build import cythonize
setup(
name='test_cython',
ext_modules=cythonize("test_cython.pyx"),
)
之后再通过命令行在当前目录生成编译的C文件,之后会发现多了.c文件和.pyd文件
python setup.pyx build_ext --inplace
再编写一个测试代码进行速度测试
import test_cython
import time
start_time=time.time()
result=check.test_func()
print(result)
print("用时:",time.time()-start_time)
测试的结果如下,
Python(3.9)用时24-26秒,Cython稳定在7.2秒,Java则是在7-10秒左右波动
附:boundscheck和wraparound两个注解的作用
https://zhuanlan.zhihu.com/p/657040768
Python 的列表和数组通常会在访问元素时执行边界检查,以确保不会访问超出范围的元素。这会导致额外的性能开销。在 Cython 中,你可以使用数组的 “boundscheck” 属性和 “wraparound” 属性来控制边界检查