加速Python的几种方法
前言
注意以下内容均为本人主观操作感受,运行环境为Win10+python3.7_32位,仅供本人日后查阅
众所周知,python是一门优美而简洁的编程语言,其最大的不足在于运行速度过于感人,而C语言作为最接近底层的高级语言,速度快是他最大的优势。python被誉为胶水语言,应该有办法和C语言胶和起来,我查阅资料发现python调用C语言最普遍的方式大概有三种:
- 将C语言的函数编译成dll动态链接库,然后通过python的ctypes模块对dll进行调用。
- 借由cython先将python代码编译成C语言代码,然后再讲C代码编译成.pyd文件直接在python中导入进行调用,cython本身有一定语法,不过不会一般也不太影响使用。
- 使用Swig可以将C代码直接封装成python代码,过程略为复杂,优点是可以为多种语言提供接口。
以上几种方法的原理和过程下图很好的解释了:
本文以斐波拉契函数为例,探究python和C的合璧之法。
import time
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
start=time.time()
print("计算结果:%d" % fib(40))
end=time.time()
print('python time cost:',end-start)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int fib(int n)
{
if (n == 0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
int main()
{
clock_t start, end;
start = clock();
printf("计算结果:%d\n", fib(40));
end = clock();
printf("C cost time:%f", (double)(end - start) / CLOCKS_PER_SEC);
system("pause");
return 0;
}
通过运行上面的代码可以得到运行时间:
- 纯python:
计算结果:102334155
python time cost: 87.49325394630432
- 纯C语言:
计算结果:102334155
C cost time:0.863000
差不多差了两个数量级吧。。。
1. 通过ctypes调用dll
首先得先生成一个dll,你可以在ide里新建一个dll工程进行创建。更简单的做法是利用下面的gcc命令直接在cmd里进行编译,但前提是你已经装了Mingw或者并Mingw-w64且设置了环境变量,其中有个比较坑的地方就是你编译出来的dll位数要和你的python位数要匹配,像我笔记本上装的是Mingw-w64,而pyhton是32位的,32的python无法调用64位的dll,而这位Mingw-w64虽说同时支持64位和32位,但是前提是安装时勾选的是sjlj而不是seh,seh不支持32位!
- 安装Mingw建议用下面这位up的工具,简单快捷。
【一键】20秒配置VScodeC语言C++开发环境! - 安装Mingw-w64可以参考:Mingw-w64安装
用于编译dll的c语言代码只需给出函数就好了:
int fib(int n)
{
if(n == 0)
{
return 0;
}
else if(n == 1)
{
return 1;
}
else
{
return fib(n-1) + fib(n-2);
}
}
下面是cmd中将C原文件编译成dll的命令:
gcc -fPIC -shared name.c -o name.dll
如果是sjlj的Mingw-w64加上-m32可以编译出32位dll:
gcc -fPIC -shared -m32 name.c -o name.dll
然后在python中导入ctypes模块就可以调用dll了:
import ctypes
import time
start=time.time()
fibpy=ctypes.CDLL('fibpy.dll')
print("计算结果:%d" % fibpy.fib(40))
end=time.time()
print('dll time cost:',end-start)
ctype调用dll运行时间:
- 计算结果:102334155
- dll time cost: 0.9999649524688721
因为只有一个函数,还调用的是C语言的,所以速度和纯C差不多。
2. cython
先给出官方的一个教程:官方教程
使用cython首先要安装cython模块:
pip install Cython
然后你需要安装Mingw进行一番配置或者直接下载安装Visual Studio安装C++环境,因为我用的是Mingw-win64,所以自然就先抛弃后面那个巨无霸选用前者,一番尝试后发现还是Visual Studio真香。当然可能单纯的是因为Mingw-win64不支持而已,用Mingw的话应该是可以的(但愿)。
安装完环境后就可以直接把之前的纯python代码中的函数部分进行cython转换,不过需要现将其后缀名改为.pyx,然后需要再创建一个setup.py文件,其中写入代码:
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("fibcy.pyx")
)
然后还是cmd中执行:
python setup.py build_ext --inplace
这时应该会生成一个.pyd文件,这个文件本质和前面的dll应该差不多,可以通过import在python中使用:
import time
import fibcy
start=time.time()
print("计算结果:%d" % fibcy.fib(40))
end=time.time()
print('cython time cost:',end-start)
运行结果:
- 计算结果:102334155
- cython time cost: 15.108871698379517
可以看到即使是在没有使用任何cython语法的情况下,cython的加速能力也是令人惊叹的,运行速度提高了一个数量级左右!
3. Swig
同样先给出一个教程参考:Swig教程
其实swig的原理和前面也差不多,但是过程更为复杂。首先当然要安装swig,下载解压后同样添加到系统环境变量。
然后要准备的有C语言源文件,一个.i文件,还有一个setup.py文件。
#include <stdio.h>
int fib(int n)
{
if (n == 0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
%module fib
%{
#include <stdio.h>
%}
extern int fib(int n);
from setuptools import setup,Extension
setup( name='fib',
version='1.0',
ext_modules=[Extension('_fib', ['fib.c', 'fib.i'])]
)
最后在命令行运行setup.py:
python setup.py build_ext --inplace
最后生成的文件中应该有一个python文件,可以直接进行调用,除此之外我发现还有一个.pyd文件,果然和前面的方法都差不多呢,导入运行方法也和之前相同。
运行结果:
- 计算结果:102334155
- swig time cost: 0.6825790405273438
居然比纯C语言还要快!大概是因为去掉了计时模块的缘故,可能python的计时模块比C的要快?结果还是很匪夷所思呢?
4. namba的@jit加速
namba加速不同于以上3种方法,上面3种方法多多少少会和C语言扯上点关系。numba则是直接在python源文件里通过加上一些代码直接运行并获得加速效果,本质原理也是使数据向静态靠拢,但是实现过程与上面3种方法相比更简单,可以参考官方入门教程
下面具体看一下实现过程:
首先肯定是要先安装numba包
pip install numba
然后只需在Python代码中导入numba并用jit修饰相应的函
import time
from numba import jit
@jit(nopython=True)
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
start=time.time()
print("计算结果:%d" % fib(40))
end=time.time()
print('numba@jit time cost:',end-start)
运行结果:
- 计算结果:102334155
- numba@jit time cost: 1.2832109928131104
加速效果可以说是相当惊人的,比cython要快,而且操作也特别简单,不用导来导去,和numpy相配合使用可以使运行速度得到极大的提升。
总结
方案 | 运行时间 |
---|---|
纯Python | 87.49325394630432 |
纯C语言 | 0.863000 |
ctypes | 0.9999649524688721 |
cython | 15.108871698379517 |
swig | 0.6825790405273438 |
numba | 1.2832109928131104 |
python结合c语言的三种方法中ctypes方法最简单,将C代码直接编译成dll即可使用,cython可以为现有的python代码加速,swig可以直接将C转换为python,就是有点麻烦,但速度是最快的。numba的jit加速比上面三种方法都要更简单,只需在python源码里加几句就行了,而且速度也很可观。