使用C语言扩展python加速运算

背景:

Python语言在数据处理流程中使用非常方便,但是众所周知,python虽然简单方便,但是却避免不了解释性语言的弊端——运行速度慢。而c语言较为底层,在速度方面能够超越大部分编程语言。使用c语言扩展python接口能够充分利用python的简单易用的优点,同时拥有c语言运行速度快的优点。

本文将通过介绍我们工程中经常使用的python字典数据类型的数据的处理,了解c语言扩展python接口的流程。使大家能够对相关的转换函数进行转换尽快上手,提高程序的运行速度。

流程:

网上的教程有很多,但是使用的流程大致分为以下几步:

1、模块初始化:定义模块名字,并指定模块定义的函数。

2、函数列表:指定模块包含哪些接口,接口名字,接口实际调用的函数。

3、函数实现:函数的具体实现。

4、编译运行

模块初始化:

函数名和文件名保持一致,本文使用的文件名为AI.c则函数名为initAI

代码示例:

void initAI() {

Py_InitModule("AI", AIMethods);

}

代码说明:

初始化模块,模块名为AI,其定义的接口在AIMethods函数中定义。

函数列表:

定义模块接口,指定模块包含哪些接口,接口名字,接口实际调用的函数,参数传递方式等。最后一个必须增加一行NULL。

代码示例:

static PyMethodDef

AIMethods[] = {

{"feature_get_exponential_sum_with_proportion",feature_get_exponential_sum_with_proportion,METH_VARARGS},

,

};

代码说明:

定义名为“feature_get_exponential_sum_with_proportion”的接口(名字可以和实际调用的函数名不一样),实际调用的函数为feature_get_exponential_sum_with_proportion,,METH_VARARGS是函数传递参数的方式。

函数实现:

本示例中使用的函数在python中的代码实现如下:

def feature_get_exponential_sum_with_proportion(data, segment_pair,newsegment):

"""

interest with exponential function decay

get the exponential_sum from segment_pair[0] withthe segment_pair[1]

:param data: dict

:param segment_pair: first item is origin data andis list, second item is list and is condition

:param new_segment: list, new key corresponding tothe key of transformed data

:return: data self

"""

data1, data2 = data[segment_pair[0]],data[segment_pair[1]]

s = 0

for i, value in enumerate(data1):

s += 1.0 / (np.log10(i +1) + 1) * value / data2[i] / len(data1)

data[newsegment] = [s]

return data

可以看出代码非常简洁明了,短短五六行就完成了整个函数的具体实现,但是当我们使用c语言来对这个函数进行实现呢?代码如下:

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

{

PyObject *dict;

PyObject * py_key_pair;

char * new_key;

PyObject * py_value1;

PyObject * py_value2;

if(!PyArg_ParseTuple(args, "OOs",&dict,&py_key_pair,&new_key))

returnNULL;

PyObject * k1 =PyTuple_GetItem(py_key_pair,0);

PyObject * k2 = PyTuple_GetItem(py_key_pair,1);

char * key1 =PyString_AS_STRING(k1);

char * key2 =PyString_AS_STRING(k2);

// printf("%s%s\n",key1,key2);

py_value1 =PyDict_GetItemString(dict,key1); //

py_value2 =PyDict_GetItemString(dict,key2); //

int v1_len =PyList_Size(py_value1);

int i;

PyObject* pList =PyList_New(0); // new reference:empty list

//assert(PyList_Check(pList));

doubleresult=0;

for(i=0;i

doublev1,v2;

PyObject*v1_temp;//

v1_temp= PyList_GetItem(py_value1,i);

v1=PyFloat_AsDouble(v1_temp);

// printf("%d%f\n",i,v1);

PyObject* v2_temp;

v2_temp= PyList_GetItem(py_value2,i);

v2= PyFloat_AsDouble(v2_temp);

result+=(1.0/(log10(i+1)+1)*v1/v2/v1_len);

}

PyList_Append(pList,Py_BuildValue("d", result));

PyDict_SetItemString(dict,new_key,Py_BuildValue("O",pList));

Py_DECREF(pList);

Py_INCREF(dict);

return dict;

}

我们可以看到,代码相对来说复杂了很多,但是仔细看可以看出,多出的代码更多的在于类型的转换,即将python类型的数据转换成c语言类型的数据。

代码说明:

if (!PyArg_ParseTuple(args, "OOs",&dict,&py_key_pair,&new_key))

returnNULL;

对函数的参数进行解析,“OOs”指明参数的数据类型,“O”代表PyObject数据类型,“s”指明字符串数据类型。当然,还有其他的数据类型,请查看本文给出的参考文档。

PyObject * k1 = PyTuple_GetItem(py_key_pair,0);

PyObject * k2 = PyTuple_GetItem(py_key_pair,1);

py_key_pair是tuple数据类型,上面两行代码得到tuple数据类型的第一项和第二项数据。

char * key1 = PyString_AS_STRING(k1);

char * key2 = PyString_AS_STRING(k2);

将它们从PyString数据类型转换c语言能够使用的string字符串数据类型。

py_value1 = PyDict_GetItemString(dict,key1); // value_K_i

py_value2 = PyDict_GetItemString(dict,key2); // value_K_i

根据key1,和key2得到dict字典数据类型的数据。

//上面几行可以用更加简单的操作来实现。如下:

//Py_value1 = PyDict_GetItem(dict,k1);

//Py_value2 = PyDict_GetItem(dict,k2);

int v1_len = PyList_Size(py_value1);

得到list的长度。

PyObject* pList = PyList_New(0);

生成一个空的list。

PyList_Append(pList, Py_BuildValue("d", result));

往pList中添加数据,数据类型为double,数值为result

PyDict_SetItemString(dict,new_key,Py_BuildValue("O",pList));

增加一个键值对,键为new_key,值为pList

Py_DECREF(pList);

Py_INCREF(dict);

对dict增加一个引用计数

return dict;

返回字典。

编译链接:

gcc -fpic -c –I /usr/include/python2.2 -I /usr/lib/python2.2/config AI.c这个操作的-I命令可以指定编译所依赖的头文件所在的位置

gcc -shared -o AI.so AI.o

这部分也可以通过标准python构建系统distutils从setup.py编译C扩展,这相当方便。这里不对其进行介绍。

运行结果:(图)

运行比较:第一个时间是使用python代码运行的时间,第二个是c语言扩展python接口运行的时间,可以看到c语言实现的代码有了明显的加速。

总结:

C语言扩展python接口从实际运行中可以发现确实增速不少,所以当我们的模型性能遇到瓶颈时,不妨通过C语言修改特征的转换函数。但是实际工程量方面却增加很多,所有实际运作时还是要兼顾两个方面的影响。

Tips:遇到的几个坑

1,Error:takes no keyword argument报错:如果使用METH_VARARGS参数传递方式,那么c语言传递参数的时候不能指定参数名,所以接口不能使用类似func(a=1)这样的参数,使用func(1),如果需要使用键值对参数,则需要使用METH_KEYWORDS。

2,TypeError: bad argument type for built-in operation:可能是GetItem时访问数据越界造成的。

3,引用计数增加很重要,没有正确增减对象引用计数将会导致致命错误。

4,float类型在数据精度要求非常高的情况下会出现精度不准确的情况,转换成double数据类型可以有效的解决。

参考文档:

1.官方文档:https://docs.python.org/2.7/c-api/

2.官方示例:https://en.wikibooks.org/wiki/Python_Programming/Extending_with_C

3.用C语言扩展Python的功能https://www.ibm.com/developerworks/cn/linux/l-pythc/

4.Extending C program using Python unableto parse dictionary to C function using swighttps://stackoverflow.com/questions/26246195/extending-c-program-using-python-unable-to-parse-dictionary-to-c-function-using

5,使用numpy数据类型https://segmentfault.com/a/1190000000479951

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180620G23OTK00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券