Python与C/C++混合编程的应用

栏目: C++ · 发布时间: 7年前

内容简介:TIOBE每个月都会新鲜出炉一份流行编程语言排行榜,这里会列出最流行的20种语言。排序说明不了语言的好坏,反应的不过是某个软件开发领域的热门程度。语言的发展不是越来越common,而是越来越专注领域。有的语言专注于简单高效,比如Python,内建的list,dict结构比c/c++易用太多,但同样为了安全、易用,语言也牺牲了部分性能。在有些领域,比如通信,性能很关键,但并不意味这个领域的coder只能苦苦挣扎于c/c++的陷阱中,比如可以使用多种语言混合编程。我看到的一个很好的Python与C/C++混合

TIOBE每个月都会新鲜出炉一份流行编程语言排行榜,这里会列出最流行的20种语言。排序说明不了语言的好坏,反应的不过是某个软件开发领域的热门程度。语言的发展不是越来越common,而是越来越专注领域。有的语言专注于简单高效,比如Python,内建的list,dict结构比c/c++易用太多,但同样为了安全、易用,语言也牺牲了部分性能。在有些领域,比如通信,性能很关键,但并不意味这个领域的coder只能苦苦挣扎于c/c++的陷阱中,比如可以使用多种语言混合编程。

我看到的一个很好的 Python 与C/C++混合编程的应用是NS3(Network Simulator3)一款网络模拟软件,它的内部计算引擎需要用高性能,但在用户建模部分需要灵活易用。NS3的选择是使用C/C++来模拟核心部件和协议,用python来建模和扩展。

这篇文章介绍Python和C/C++三种混合编程的方法,并对性能加以分析。

混合编程的原理

首先要说一下Python只是一个语言规范,实际上python有很多实现:CPython是标准Python,是由C编写的,python脚本被编译成CPython字节码,然后由虚拟机解释执行,垃圾回收使用引用计数,我们谈与C/C++混合编程实际指的是基于CPython解释上的。除此之外,还有Jython、IronPython、PyPy、Pyston,Jython是 Java 编写的,使用JVM的垃圾回收,可以与Java混合编程,IronPython面向.NET平台。

python与C/C++混合编程的本质是python调用C/C++编译的动态链接库,关键就是把python中的数据类型转换成c/c++中的数据类型,给编译函数处理,然后返回参数再转换成python中的数据类型。

python中使用ctypes moduel,将python类型转成c/c++类型

首先,编写一段累加数值的c代码:

extern "C"

{

int addBuf(char* data, int num, char* outData);

}

int addBuf(char* data, int num, char* outData)

{

for (int i = 0; i < num; ++i)

{

outData[i] = data[i] + 3; 

}

return num;

}

然后,将上面的代码编译成so库,使用下面的编译指令

>gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC addbuf.c -o addbuf.o

最后编写python代码,使用ctypes库,将python类型转换成 c语言 需要的类型,然后传参调用so库函数:

from ctypes import * # cdll, c_int

lib = cdll.LoadLibrary('libmathBuf.so')

callAddBuf = lib.addBuf

num = 4

numbytes = c_int(num)

data_in = (c_byte * num)()

for i in range(num):

data_in[i] = i

data_out = (c_byte * num)()

ret = lib.addBuf(data_in, numbytes, data_out)  #调用so库中的函数

在C/C++程序中使用Python.h,写wrap包装接口

这种方法需要修改c/c++代码,在外部函数中处理入/出参,适配python的参数。写一段c代码将外部入参作为 shell 命令执行:

#include <Python.h>

static PyObject* SpamError;

static PyObject* spam_system(PyObject* self, PyObject* args)

{

const char* command;

int sts;

if (!PyArg_ParseTuple(args, "s", &command))  //将args参数按照string类型处理,给command赋值

return NULL;

sts = system(command); //调用系统命令

if (sts < 0) {

PyErr_SetString(SpamError, "System command failed");

return NULL;

}

return PyLong_FromLong(sts);    //将返回结果转换为PyObject类型

}

//方法表

static PyMethodDef SpamMethods[] = {

{"system", spam_system, METH_VARARGS,

"Execute a shell command."},

{NULL, NULL, 0, NULL}

};

//模块初始化函数

PyMODINIT_FUNC initspam(void)

{

PyObject* m;

//m = PyModule_Create(&spammodule); // v3.4

m = Py_InitModule("spam", SpamMethods);

if (m == NULL)

return;

SpamError = PyErr_NewException("spam.error",NULL,NULL);

Py_INCREF(SpamError);

PyModule_AddObject(m,"error",SpamError);

}

处理上所有的入参、出参都作为PyObject对象来处理,然后使用转换函数把python的数据类型转换成c/c++中的类型,返回参数按相同方式处理。比第一种方法多了初始化函数,这部分是把编译的so库当做python module所必需要做的。

python这样使用:

imoprt spam

spam.system("ls")

使用c/c++编写python扩展可以参见: http://docs.python.org/2.7/extending/extending.html

使用SWIG,来生成独立的wrap文件

这种方式并不能算是一种新方式,实际上是基于第二中方式的一种包装。SWIG是个帮助使用C或者C++编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。SWIG能应用于各种不同类型的语言包括常用脚本编译语言例如Perl, PHP, Python, Tcl, Ruby, PHP,C#,Java,R等。

操作上,是针对c/c++程序编写独立的接口声明文件(通常很简单),swig会分析c/c++源程序自动分析接口要如何包装。在指定目标语言后,swig会生成额外的包装源码文件。编译so库时,把包装文件一起编译、连接即可。看个c代码例子:

int system(const char* command)

{

sts = system(command);

if (sts < 0) {

return NULL;

}

return sts;

}

c源码中去掉适配python的包装,仅定义system函数本身,这比第二种方式简洁很多,并且剔除了c代码与python的耦合代码,是c代码通用性更好。

然后编写swig接口声明文件spam.i:

%module spam

%{

#include "spam.h"

%}

%include "spam.h"

%include "typemaps.i"

int system(const char* INPUT);

这是一段语言无关的模块声明,要创建一个叫spam的模块,对system做一个声明,主要是声明参数作为入参使用。然后执行swig编译程序:

>swig -c++ -python spam.i

swig会生成spam_wrap.cxx和spam.py两个文件。先看spam_wrap.cxx,这个生成的文件很长,但关键的就是对函数的包装:

Python与C/C++混合编程的应用

包装函数传入的还是PyObejct对象,内部进行了类型转换,最终调了源码中的system函数。

生成的了另一个spam.py实际上是对so库又用python包装了一层(实际比较多余):

Python与C/C++混合编程的应用

这里使用_spam模块,这里实际上是把扩展命名为了_spam。关于swig在python上的应用可以参见: http://www.swig.org/Doc1.3/Python.html

下面就是编译和安装python 模块,Python提供了distutils module,可以很方便的编译安装python的module。像下面这样写一个安装脚本setup.py:

Python与C/C++混合编程的应用

执行 python setup.py build,即可以完成编译,程序会创建一个build目录,下面有编译好的so库。so库放在当前目录下,其实Python就可以通过import来加载模块了。当然也可以用 python setup.py install 把模块安装到语言的扩展库——site-packages目录中。关于build python扩展,可以参考 https://docs.python.org/2/extending/building.html#building

混合编程性能分析

混合编程的使用场景中,很重要一个就是性能攸关。那么这小节将通过几个小实验验证下混合编程的性能如何,或者说怎样写程序能发挥好混合编程的性能优势。

我们使用冒泡 排序 算法来验证性能。

1)实验一 使用冒泡程序验证python和c/c++程序的性能差距

python版冒泡程序:

def bubble(arr,length):

j = length - 1

while j >= 0:

i = 0

while i < j:

if arr[i] > arr[i+1]:

tmp = arr[i+1]

arr[i+1] = arr[i]

arr[i] = tmp

i += 1

j -= 1

c语言版冒泡排序

void bubble(int* arr,int length){

int j = length - 1;

int i;

int tmp;

while(j >= 0){

i = 0;

while(i < j){

if(arr[i] > arr[i+1]){

tmp = arr[i+1];

arr[i+1] = arr[i];

arr[i] = tmp;

}

i += 1;

}

j -= 1;

}

}

使用一个长度为100内容固定的数组,反复排序10000次(每次排序后,再把数组恢复成原始序列),记录执行时间:

在相同的机器上多次执行,Python版执行时间是10.3s左右,而c语言版本(未使用任何优化编译参数)执行时间只有0.29s左右。相比之下python的性能的确差很多(主要是python中list的操作跟c的数组相比,效率差非常多),但python中很多扩展都是c语言写的,目的就是为了提升效率,python用于数据分析的numpy库就拥有不错的性能。下个实验就验证,如果python使用c语言版本的冒泡排序扩展库,性能会提升多少。

2)实验二 python语言使用ctypes方式调用

这里直接使用c_int来定义了数组对象,这也节省了调用时数据类型转换的开销:

import time

from ctypes import *

IntArray100 = c_int * 100

arr = IntArray100(87,23,41, 3, 2, 9,10,23,0,21,5,15,93, 6,19,24,18,56,11,80,34, 5,98,33,11,25,99,44,33,78,

52,31,77, 5,22,47,87,67,46,83, 89,72,34,69, 4,67,97,83,23,47, 69, 8, 9,90,20,58,20,13,61,99,7,22,55,11,30,56,87,29,92,67,

99,16,14,51,66,88,24,31,23,42,76,37,82,10, 8, 9, 2,17,84,32,66,77,32,17, 5,68,86,22, 1, 0)

... ...

if __name__ == "__main__":

libbubble = CDLL('libbubble.so')

time1 = time.time()

for i in xrange(100000):

libbubble.initArr(arr1,arr,100)

libbubble.bubble(arr1,100)

time2 = time.time()

print time2 - time1

再次执行:

为了减少误差,把循环增加到10万次,结果c原生程序使用优化参数编译后用时0.65s左右。python使用c扩展后(相同编译参数)执行仅需2.3s左右。

3)实验三 在c语言中使用PyObject处理入参

这种方式是在python中依然使用list装入待排序数列,在c函数中把list赋值给数组,再进行排序,排好序后,再对原始list赋值。循环排序10万次,执行用时1.0s左右。

4) 实验四 使用swig来包装c方法

在接口文件中声明%array_class(int,intArray);然后在Python中使用initArray来作为数组,同样修改成10万次排序。python版本的程序(相同编译参数)执行仅需0.7s左右,比c原生程序慢大概7%。

结论

1.python 的list效率非常低,在高性能场景下避免对list大量循环、取值、赋值操作。如需要最好使用ctype中的数组,或者是用c语言来实现。

2.应该把耗时的cpu密集型的逻辑交给c/c++实现,python使用扩展即可。

Linux公社的RSS地址https://www.linuxidc.com/rssFeed.aspx

本文永久更新链接地址: https://www.linuxidc.com/Linux/2018-10/155103.htm


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Practical Django Projects, Second Edition

Practical Django Projects, Second Edition

James Bennett / Apress / 2009 / 44.99

Build a django content management system, blog, and social networking site with James Bennett as he introduces version 1.1 of the popular Django framework. You’ll work through the development of ea......一起来看看 《Practical Django Projects, Second Edition》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

URL 编码/解码
URL 编码/解码

URL 编码/解码