Python 的性能问题一直开发者吐槽的诟病,但 Python 语言的语法简洁和快速开发又让开发者深受喜好。如何提升 Python 程序运行性能,一直是 Python 开发者的课题。html
本篇文章介绍一种方法,经过 Python 的 C扩展程序,来提升运行性能。Python 最受欢迎的解释器是 CPython,是C语言编写。因此 Python 和 C语言自己有很好的兼容性,两者也能够互相调用。python
接下来就分享一下如何用Python调用C程序。git
本文的环境: mac + Python3。 程序以 斐波拉契 算法程序为例。 示例完整代码地址: github.com/fuzctc/tc-p…github
首先介绍最基本的方式,经过Python/C API来实现。算法
Python extension module 是Python官方提供Python之外的语言创建且可以让Python调用的module,官方文档地址: docs.python.org/3/extending… ,建议你们在写C扩展程序的时候,都拜读一下。api
首先给你们展现一下 Python 版的斐波拉契:app
def fib_recursive(n):
if n < 2:
return n
return fib_recursive(n - 1) + fib_recursive(n - 2)
start_ts = time.time()
print(fib_recursive(35))
print(time.time() - start_ts)
# 运行结果:
# 9227465
# 3.8501219749450684
复制代码
若是将此程序改成 C 语言程序,命名为 speedup_fib.c
,如何进行改写,须要以下步骤:函数
Python/C API 是C语言里创建 Python extension module 的煤介,必须先引入 Python.h 头文件。工具
// content of speedup_fib.c
#include <Python.h>
long long _fib(long long n){
if(n < 2)
return n;
else
return _fib(n-1) + _fib(n-2);
};
复制代码
引入 Python.h
,写一个 C 语言版本的 斐波拉契。性能
在 Python 中一切皆对象,对应到C语言中是 PyObject
,因此要将原来的function包装一下,让参数和返回值均为 PyObject
。
官方提供了三种参数形式:
其中 args 表示 positional arguments, kwargs 是 keyword arguments.
要把 Python 中的这些 arguments 转为 本身定义的 C 语言参数的话,还须要调用几个API,具体能够参考官方文档 docs.python.org/3/c-api/arg… 截图以下。
经常使用的就是2个。
第一个处理常规带 positional arguments ,第二个处理带 positional arguments 和 keyword arguments.
其中 format
是格式化参数类型,好比长整型或者字符串等,具体参考官方文档: docs.python.org/2.0/ext/par…
由于 斐波拉契 程序没有键值对参数,因此采用第一个函数,且参数须要格式化为长整型,查询文档长整型是 l
表示。
根据上述解释后,最终包装后的程序为:
// content of speedup_fib.c
static PyObject *fib(PyObject *self, PyObject *args) {
long long n; // 定义 参数
long long res; // 定义返回值
// 将参数进行包装,并格式化为长整型l,若是包装失败,则返回NULL.
if (!PyArg_ParseTuple(args, "l", &n))
return NULL;
// 调用C语言版本的斐波拉契,同时传入包装好的参数n
res = _fib(n);
// 将返回值用 Py_BuildValue 包成 PyObject 传给 Python
return Py_BuildValue("l", res);
};
复制代码
包装的 斐波拉契 函数定义为fib
,最后返回值也须要进行包装,将C语言的返回值打包成 PyObject
。
把 module 的函数进行一一包装后,要创建这个module的method列表,目的就是声明每个包装函数和C语言函数的对应关系,以及函数是以哪一种形式进行传入,格式为:
{name, method, flags, doc}
即 {名称,包装函数,哪一种argument形式, 描述}
复制代码
flags 的标识能够参考官方文档: docs.python.org/3/c-api/str…
比较多的就是 METH_VARARGS 和 METH_KEYWORDS,分别对应 args 和 keywords。
因此根据上述描述,定义的Module Methods 为:
// content of speedup_fib.c
static PyMethodDef SpeedupFibMethods[] = {
{"speedup_fib", (PyCFunction) fib, METH_VARARGS, "fast fib"},
{NULL, NULL, 0, NULL} // 以 NULL 做结
};
复制代码
创建了 Module Methods 以后,还须要定义Module的结构,须要这些信息去建立一个 module object。格式为:
{base, name, doc, size, module methods 表}
即 {PyModuleDef_HEAD_INIT, 名字, 描述, 分配内存大小, module 方法列表}
复制代码
相关定义能够参考官方文档: docs.python.org/3/c-api/mod…
根据上面描述,定义 Module 结构函数为:
// content of speedup_fib.c
static struct PyModuleDef speedup_fib_module = {
PyModuleDef_HEAD_INIT,
"speedup_fib",
"A module containing methods with faster fib.",
-1, // global state
SpeedupFibMethods
};
复制代码
接下来定义 Module Initialization Method,目的是根据module结构信息去建立一个module object,而这个module object就能够供Python调用。
须要注意Module Initialization Method必须以PyInit_开头。
// content of speedup_fib.c
PyMODINIT_FUNC PyInit_speedup_fib() {
return PyModule_Create(&speedup_fib_module);
}
复制代码
上面5步已经把 speedup_fib.c
中的代码部分写完,接下来须要建立一个 setup.py
,经过 Distutils
模块将C语言模块建立出来。
# content of setup.py
from distutils.core import setup, Extension
speedup_fib_module = Extension('speedup_fib', sources=['speedup_fib.c'])
setup(
name='SpeedupFib',
description='A package containing modules for speeding up fib.',
ext_modules=[speedup_fib_module],
)
复制代码
首先指定哪一个 C 程序中的 哪一个方法,而后调用 setup 创建扩展文件。
经过如下命令:
python3 setup.py build_ext --inplace
复制代码
会在当前文件夹创建一个 speedup_fib.cpython-37m-darwin.so
文件,接下来就能够直接在Python中进行调用。
测试一下:
from speedup_fib import speedup_fib
start_ts = time.time()
print(speedup_fib(35))
print(time.time() - start_ts)
# 运行结果:
# 9227465
# 0.054654836654663086
复制代码
对比以前的Python程序,速度快了80倍,若是n更大,速度差距会更大。
若是以为第一种方式过于繁琐,接下来介绍 ctypes,ctypes是Python提供的一个libray,可让Python进入外部的dynamic-link library (DLL) 或 shared library 来调用其中的函数。
这样再也不须要关注Python与C相关的API,专一写C函数便可。
这一步在任何方式中都不能省,首先仍是编写C语言版本的程序。
// content of speedup_fib.c
long long fib(long long n){
if(n < 2)
return n;
else
return fib(n-1) + fib(n-2);
};
复制代码
是否是很简单,只须要专一写函数,连头文件都不须要。
这一步须要用到 gcc 工具,若是没有的,须要先安装。
gcc -shared -fPIC speedup_fib.c -o speedup_fib.so
复制代码
经过上述命令将 speedup_fib.c 产生一个 speedup_fib.so 文件。
接下来就简单了,只须要 ctypes 提升的方法,引入 speedup_fib.so 文件,而后就能够进行 Python 运行了。
# content of fib.py
from ctypes import *
func = cdll.LoadLibrary('./speedup_fib.so')
start_ts = time.time()
print(func.fib(35))
print(time.time() - start_ts)
复制代码
运行上述 fib.py 文件 获得结果:
9227465
0.06056809425354004
复制代码
SWIG (Simplified Wrapper and Interface Generator) 是更加通用和全面的工具,支持 Python、Perl、Ruby等多种语言.
首先须要先安装 SWIG,若是是 mac 环境 直接 brew install swig
便可,window环境参考官网: www.swig.org/Doc3.0/Pref…
这一步不可少,可是在swig里须要命令为头文件.h。
// content of speedup_fib.h
long long fib(long long n){
if(n < 2)
return n;
else
return fib(n-1) + fib(n-2);
};
复制代码
接下来创建接口文件,也能够说是描述接口的档案,习惯命名为 *.i or *.swg。
接下来定义一个 speedup_fib.i
// content of speedup_fib.i
/* 定义 module名称 */
%module speedup_fib
/*导入定义的 speedup_fib.h*/
%{
#include "speedup_fib.h"
%}
/* 告诉 SWIG 定义的 function 或 variable */
long long fib(long long n);
复制代码
上述定义的 speedup_fib.i
中,第一步定义Module名称,第二步引入定义的 speedup_fib.h
,里面swig会调用里面的函数,第三步是声明函数。
经过 SWIG 将 Interface File 生成 extension module 的 speedup_fib.py 和 wrapper file 的 speedup_fib_wrap.c。
命令以下:
swig -python speedup_fib.i
复制代码
当前文件夹会多2个文件: speedup_fib.py 和 speedup_fib_wrap.c。
这一步跟 Python/C API 建立扩展模块,用用 setup.py 和 Distutils 创建 shared library:
# content of setup.py
from distutils.core import setup, Extension
# Extension module name 要有底线前缀
speedup_fib_module = Extension('_speedup_fib', sources=['speedup_fib_wrap.c'])
setup(
name='SpeedupFib',
description='A package containing modules for speeding up performance.',
ext_modules=[speedup_fib_module],
)
复制代码
注意 Extension module name 要有底线前缀。
接下来下面命令:
python3 setup.py build_ext --inplace
复制代码
会在当前文件夹创建一个 _speedup_fib.cpython-37m-darwin.so
文件,接下来就能够直接在Python中进行调用。
# content of fib.py
from speedup_fib import fib
start_ts = time.time()
print(fib(35))
print(time.time() - start_ts)
复制代码
运行 fib.py, 运行结果为:
9227465
0.05449485778808594
复制代码
这一篇文章就到这里,经过上面三种方法,能够经过C扩展程序提升Python程序运行性能。示例中完整代码地址: github.com/fuzctc/tc-p…
有关更多提升 Python程序性能文章,请关注公众号: