摘要:通过执行时间对比可以发现调用函数来扩展功能可以大大提高执行速度,而自带的库由于在源生代码上进行封装,执行时间会高于源生代码扩展方式,但库使用方便,特别适合应用在第三方封装代码,提供动态链接库和调用文档的场合。
前言
当我们提到一门编程语言的效率时,通常包含了开发效率和运行效率这两层意思。Python作为一门高级语言,它功能强大,易于掌握,能够快速的开发软件,“life is short,we use python!”,想必这些优点是毋庸置疑的,但是作为一门解释性语言,执行速度的局限性导致在处理某些高频任务时存在不足。
由于Python本身由C语言实现的,开发性能要求较高的程序模块可以通过扩展运行效率更高的C语言来弥补自身的弱点。另外有些算法已经有开源的C库,那么也没必要用Python重写一份,只需要通过Python进行C库的调用即可。
本文通过实例介绍如何在Python 程序中整合既有的C语言模块,从而充分发挥Python 语言和 C 语言各自的优势。
使用Python编写一个递归函数和循环函数,应用Python的计时库timeit测试函数执行10000次所需要的时间分别为57ms和41ms。
实现代码如下:
from timeit import timeit def factorial(n): if n<2:return 1 return factorial(n-1)*n def rooporial(n): if n<2:return 1 ans = 1 for i in range(1,n+1): ans *=i return ans if __name__ == "__main__": print "factorial",factorial(20),timeit("factorial(20)","from __main__ import factorial",number=10000) #timeit(‘函数名’,‘运行环境’,number=运行次数) print "rooporial",rooporial(20),timeit("rooporial(20)","from __main__ import rooporial",number=10000)
打印返回:
factorial 2432902008176640000 0.0578598976135 factorial 2432902008176640000 0.0410023010987
当然递归方法使程序的结构简洁,但由于它逐层深入调用的机制使得执行效率不如循环,以下的测试可以发现每一次递归是新一次的函数调用,会产生新的局部变量,增加了执行时间。但是即使使用For循环实现也需要41ms时间,接下来我们尝试更快的实现方式。
测试代码如下:
def up_add_down(n): print("level %d: n location %p ",n,id(n)) if n<=4:up_add_down(n+1) print("level %d: n location %p ",n,id(n)) return
打印返回:
("level %d: n location %p ", 0, 144136380) ("level %d: n location %p ", 1, 144136368) ("level %d: n location %p ", 2, 144136356) ("level %d: n location %p ", 3, 144136344) ("level %d: n location %p ", 4, 144136332) ("level %d: n location %p ", 5, 144136320) ("level %d: n location %p ", 5, 144136320) ("level %d: n location %p ", 4, 144136332) ("level %d: n location %p ", 3, 144136344) ("level %d: n location %p ", 2, 144136356) ("level %d: n location %p ", 1, 144136368) ("level %d: n location %p ", 0, 144136380)
Python在设计之初就考虑到通过足够抽象的机制让C和C++之类的编译型的语言导入到Python脚本代码中,在Python的官方网站上也找到了扩展和嵌入Python解释器对应的方法。链接为:https://docs.python.org/2.7/e...。这里介绍下如何将C编写的函数扩展至Python解释器中。
(1)将C编写的递归函数存为wrapper.c,作为Python的扩展库。同时需要对C函数增加一个型如PyObject* Module_func()的封装接口,该接口用于Python解释器的交互。将封装接口加入至型如PyMethodDef ModuleMethods[]的数组中,Python解释器能够从数组中导入并调用到封装接口。最后是实现对扩展库的初始化函数,调用Py_InitModule()函数,把扩展库和ModuleMethods[]数组的名字传递进去,以便于解释器能正确的调用库中的函数。
wrapper.c实现代码如下:
#includeunsigned long long factorial(int n) { if(n<2)return 1; return factorial(n-1)*n; } PyObject* wrap_fact(PyObject* self,PyObject* args) { int n; unsigned long long result; if(!PyArg_ParseTuple(args,"i:fact",&n))return NULL;//i 整形 result = factorial(n); return Py_BuildValue("L",result);//L longlong型 } static PyMethodDef wrapperMethods[] = { {"fact",wrap_fact,METH_VARARGS,"Caculate N!"},//METH_NOARGS无需参数/METH_VARARGS需要参数; {NULL,NULL}, }; int initwrapper() { PyObject* m; m = Py_InitModule("wrapper",wrapperMethods);//参数:扩展库名称/库所包含的方法 return 0; }
注:初始化函数名必须为initmodule_name这样的格式
(2)安装python-dev包含Python.h头文件
安装命令:sudo apt-get python-dev
(3)在linux环境下wrapper.c编译成动态链接库wrapper.so
编译命令:gcc wrapper.c -fPIC -shared -o wrapper.so -I/usr/include/python2.7
注:虽然已经安装了python-dev,但编译时仍然提示“Python.h:没有那个文件或目录”,需要通过gcc的-I dir选项在头文件的搜索路径列表中添加dir目录
(4)Python文件中import wrapper导入动态链接库,在import语句导入库时会执行初始化函数
(5)Python文件中wrapper.fact()方式对C函数调用时,封装函数wrap_fact()先会被调用,封装函数接收到一个Python整形对象,PyArg_ParseTuple将Python整形对象转为C整形参数,然后调用C的factorial()函数并将C整数参数传入,经过运算后得到一个C长整形的返回值,Py_BuildValue把C长整形返回值转为Python的长整形对象作为最终整个函数调用的结果。
(6)timeit测试函数wrapper.fact(20)执行10000次所的时间只需要5.9ms。
factorial_rc 2432902008176640000 0.00598216056824
Python内建ctypes库使用了各个平台动态加载动态链接库的方法,并在Python源生代码基础上通过类型映射方式将Python与二进制动态链接库相关联,实现Python与C语言的混合编程,可以很方便地调用C语言动态链接库中的函数。(ctypes源码路径:/Modules/_ctypes/_ctypes.c、/Modules/_ctypes/callproc.c)
(1)将C编写的递归函数存为a.c,不需要对C函数经过Python接口封装
#include#include unsigned long long factorial(int n) { if(n<2)return 1; return factorial(n-1)*n; }
(2)在linux环境下a.c编译成动态链接库a.so
编译命令:gcc a.c -fPIC -shared -o a.so
(3)Python文件中调用动态链接库a.so,在Windows平台下,最终调用的是Windows API中LoadLibrary函数和GetProcAddress函数,在Linux和Mac OS X平台下,最终调用的是Posix标准中的dlopen和dlsym函数。ctypes库内部完成PyObject* 和C types之间的类型映射,使用时只需设置调用参数和返回值的转换类型即可。
from ctypes import cdll from ctypes import * libb = cdll.LoadLibrary("./a.so") def factorial_c(n): return libb.factorial(n) if __name__ == "__main__": libb.factorial.restype=c_ulonglong#返回类型 libb.factorial.argtype=c_int#传入类型 print "factorial_c",factorial_c(20),timeit("factorial_c(20)","from __main__ import factorial_c",number=10000)
(4)timeit测试函数libb.factorial(20)执行10000次所的时间需要8.5ms。
factorial_c 2432902008176640000 0.00857210159302
如下图所示,factorial、factorial_c、factorial_rc分别为Python脚本、ctypes 库和Python源生代码扩展方式来实现函数的执行时间。通过执行时间对比可以发现调用C函数来扩展Python功能可以大大提高执行速度,而Python自带的ctypes库由于在源生代码上进行封装,执行时间会高于源生代码扩展方式,但ctypes库使用方便,特别适合应用在第三方封装代码,提供动态链接库和调用文档的场合。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/41314.html
摘要:的批评者声称性能低效执行缓慢,但实际上并非如此尝试以下个小技巧,可以加快应用程序。使用或者机器语言扩展包来执行关键任务能极大改善性能。但是如果你把求值的结果放入一个变量中,就能提高程序的性能。 Python是一门非常酷的语言,因为很少的Python代码可以在短时间内做很多事情,并且,Python很容易就能支持多任务和多重处理。 Python的批评者声称Python性能低效、执行缓慢,...
摘要:但是语言并没有成功,究其原因,认为是其非开标识放造成的。已经成为最受欢迎的程序设计语言之一。年月,该语言作者在邮件列表上宣布将于年月日终止支持。其中很重要的一项就是的缩进规则。设计定位的设计哲学是优雅明确简单。 文本标签 换行标签 -- br 是单标签,意味着它没有结束标签。起强制换行作用 段落中的文字段落中的文字段落中的文字 水平分割线 -- hr 与br相同,也是单标签。可用来区分...
摘要:入门,第一个这是一门很新的语言,年前后正式公布,算起来是比较年轻的编程语言了,更重要的是它是面向程序员的函数式编程语言,它的代码运行在之上。它通过编辑类工具,带来了先进的编辑体验,增强了语言服务。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不觉已经到来了,总结过去的 2017,相信小伙们一定有很多收获...
摘要:入门,第一个这是一门很新的语言,年前后正式公布,算起来是比较年轻的编程语言了,更重要的是它是面向程序员的函数式编程语言,它的代码运行在之上。它通过编辑类工具,带来了先进的编辑体验,增强了语言服务。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不觉已经到来了,总结过去的 2017,相信小伙们一定有很多收获...
阅读 2520·2023-04-25 14:54
阅读 594·2021-11-24 09:39
阅读 1802·2021-10-26 09:51
阅读 3843·2021-08-21 14:10
阅读 3477·2021-08-19 11:13
阅读 2690·2019-08-30 14:23
阅读 1804·2019-08-29 16:28
阅读 3347·2019-08-23 13:45