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程序性能文章,請關注公衆號: