摘要:通過(guò)執(zhí)行時(shí)間對(duì)比可以發(fā)現(xiàn)調(diào)用函數(shù)來(lái)擴(kuò)展功能可以大大提高執(zhí)行速度,而自帶的庫(kù)由于在源生代碼上進(jìn)行封裝,執(zhí)行時(shí)間會(huì)高于源生代碼擴(kuò)展方式,但庫(kù)使用方便,特別適合應(yīng)用在第三方封裝代碼,提供動(dòng)態(tài)鏈接庫(kù)和調(diào)用文檔的場(chǎng)合。
前言
當(dāng)我們提到一門(mén)編程語(yǔ)言的效率時(shí),通常包含了開(kāi)發(fā)效率和運(yùn)行效率這兩層意思。Python作為一門(mén)高級(jí)語(yǔ)言,它功能強(qiáng)大,易于掌握,能夠快速的開(kāi)發(fā)軟件,“l(fā)ife?is?short,we?use?python!”,想必這些優(yōu)點(diǎn)是毋庸置疑的,但是作為一門(mén)解釋性語(yǔ)言,執(zhí)行速度的局限性導(dǎo)致在處理某些高頻任務(wù)時(shí)存在不足。
由于Python本身由C語(yǔ)言實(shí)現(xiàn)的,開(kāi)發(fā)性能要求較高的程序模塊可以通過(guò)擴(kuò)展運(yùn)行效率更高的C語(yǔ)言來(lái)彌補(bǔ)自身的弱點(diǎn)。另外有些算法已經(jīng)有開(kāi)源的C庫(kù),那么也沒(méi)必要用Python重寫(xiě)一份,只需要通過(guò)Python進(jìn)行C庫(kù)的調(diào)用即可。
本文通過(guò)實(shí)例介紹如何在Python 程序中整合既有的C語(yǔ)言模塊,從而充分發(fā)揮Python 語(yǔ)言和 C 語(yǔ)言各自的優(yōu)勢(shì)。
使用Python編寫(xiě)一個(gè)遞歸函數(shù)和循環(huán)函數(shù),應(yīng)用Python的計(jì)時(shí)庫(kù)timeit測(cè)試函數(shù)執(zhí)行10000次所需要的時(shí)間分別為57ms和41ms。
實(shí)現(xiàn)代碼如下:
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(‘函數(shù)名’,‘運(yùn)行環(huán)境’,number=運(yùn)行次數(shù)) print "rooporial",rooporial(20),timeit("rooporial(20)","from __main__ import rooporial",number=10000)
打印返回:
factorial 2432902008176640000 0.0578598976135 factorial 2432902008176640000 0.0410023010987
當(dāng)然遞歸方法使程序的結(jié)構(gòu)簡(jiǎn)潔,但由于它逐層深入調(diào)用的機(jī)制使得執(zhí)行效率不如循環(huán),以下的測(cè)試可以發(fā)現(xiàn)每一次遞歸是新一次的函數(shù)調(diào)用,會(huì)產(chǎn)生新的局部變量,增加了執(zhí)行時(shí)間。但是即使使用For循環(huán)實(shí)現(xiàn)也需要41ms時(shí)間,接下來(lái)我們嘗試更快的實(shí)現(xiàn)方式。
測(cè)試代碼如下:
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在設(shè)計(jì)之初就考慮到通過(guò)足夠抽象的機(jī)制讓C和C++之類的編譯型的語(yǔ)言導(dǎo)入到Python腳本代碼中,在Python的官方網(wǎng)站上也找到了擴(kuò)展和嵌入Python解釋器對(duì)應(yīng)的方法。鏈接為:https://docs.python.org/2.7/e...。這里介紹下如何將C編寫(xiě)的函數(shù)擴(kuò)展至Python解釋器中。
(1)將C編寫(xiě)的遞歸函數(shù)存為wrapper.c,作為Python的擴(kuò)展庫(kù)。同時(shí)需要對(duì)C函數(shù)增加一個(gè)型如PyObject* Module_func()的封裝接口,該接口用于Python解釋器的交互。將封裝接口加入至型如PyMethodDef ModuleMethods[]的數(shù)組中,Python解釋器能夠從數(shù)組中導(dǎo)入并調(diào)用到封裝接口。最后是實(shí)現(xiàn)對(duì)擴(kuò)展庫(kù)的初始化函數(shù),調(diào)用Py_InitModule()函數(shù),把擴(kuò)展庫(kù)和ModuleMethods[]數(shù)組的名字傳遞進(jìn)去,以便于解釋器能正確的調(diào)用庫(kù)中的函數(shù)。
wrapper.c實(shí)現(xiàn)代碼如下:
#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無(wú)需參數(shù)/METH_VARARGS需要參數(shù); ?? ?{NULL,NULL}, }; int initwrapper() { ?? ?PyObject* m; ?? ?m = Py_InitModule("wrapper",wrapperMethods);//參數(shù):擴(kuò)展庫(kù)名稱/庫(kù)所包含的方法 ?? ?return 0; }
注:初始化函數(shù)名必須為initmodule_name這樣的格式
(2)安裝python-dev包含Python.h頭文件
安裝命令:sudo apt-get python-dev
(3)在linux環(huán)境下wrapper.c編譯成動(dòng)態(tài)鏈接庫(kù)wrapper.so
編譯命令:gcc wrapper.c -fPIC -shared -o wrapper.so -I/usr/include/python2.7
注:雖然已經(jīng)安裝了python-dev,但編譯時(shí)仍然提示“Python.h:沒(méi)有那個(gè)文件或目錄”,需要通過(guò)gcc的-I dir選項(xiàng)在頭文件的搜索路徑列表中添加dir目錄
(4)Python文件中import wrapper導(dǎo)入動(dòng)態(tài)鏈接庫(kù),在import語(yǔ)句導(dǎo)入庫(kù)時(shí)會(huì)執(zhí)行初始化函數(shù)
(5)Python文件中wrapper.fact()方式對(duì)C函數(shù)調(diào)用時(shí),封裝函數(shù)wrap_fact()先會(huì)被調(diào)用,封裝函數(shù)接收到一個(gè)Python整形對(duì)象,PyArg_ParseTuple將Python整形對(duì)象轉(zhuǎn)為C整形參數(shù),然后調(diào)用C的factorial()函數(shù)并將C整數(shù)參數(shù)傳入,經(jīng)過(guò)運(yùn)算后得到一個(gè)C長(zhǎng)整形的返回值,Py_BuildValue把C長(zhǎng)整形返回值轉(zhuǎn)為Python的長(zhǎng)整形對(duì)象作為最終整個(gè)函數(shù)調(diào)用的結(jié)果。
(6)timeit測(cè)試函數(shù)wrapper.fact(20)執(zhí)行10000次所的時(shí)間只需要5.9ms。
factorial_rc 2432902008176640000 0.00598216056824
Python內(nèi)建ctypes庫(kù)使用了各個(gè)平臺(tái)動(dòng)態(tài)加載動(dòng)態(tài)鏈接庫(kù)的方法,并在Python源生代碼基礎(chǔ)上通過(guò)類型映射方式將Python與二進(jìn)制動(dòng)態(tài)鏈接庫(kù)相關(guān)聯(lián),實(shí)現(xiàn)Python與C語(yǔ)言的混合編程,可以很方便地調(diào)用C語(yǔ)言動(dòng)態(tài)鏈接庫(kù)中的函數(shù)。(ctypes源碼路徑:/Modules/_ctypes/_ctypes.c、/Modules/_ctypes/callproc.c)
(1)將C編寫(xiě)的遞歸函數(shù)存為a.c,不需要對(duì)C函數(shù)經(jīng)過(guò)Python接口封裝
#include??? #include? ?? unsigned long long factorial(int n) { ?? ?if(n<2)return 1; ?? ?return factorial(n-1)*n; }
(2)在linux環(huán)境下a.c編譯成動(dòng)態(tài)鏈接庫(kù)a.so
編譯命令:gcc a.c -fPIC -shared -o a.so
(3)Python文件中調(diào)用動(dòng)態(tài)鏈接庫(kù)a.so,在Windows平臺(tái)下,最終調(diào)用的是Windows API中LoadLibrary函數(shù)和GetProcAddress函數(shù),在Linux和Mac OS X平臺(tái)下,最終調(diào)用的是Posix標(biāo)準(zhǔn)中的dlopen和dlsym函數(shù)。ctypes庫(kù)內(nèi)部完成PyObject* 和C types之間的類型映射,使用時(shí)只需設(shè)置調(diào)用參數(shù)和返回值的轉(zhuǎn)換類型即可。
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測(cè)試函數(shù)libb.factorial(20)執(zhí)行10000次所的時(shí)間需要8.5ms。
factorial_c 2432902008176640000 0.00857210159302
如下圖所示,factorial、factorial_c、factorial_rc分別為Python腳本、ctypes 庫(kù)和Python源生代碼擴(kuò)展方式來(lái)實(shí)現(xiàn)函數(shù)的執(zhí)行時(shí)間。通過(guò)執(zhí)行時(shí)間對(duì)比可以發(fā)現(xiàn)調(diào)用C函數(shù)來(lái)擴(kuò)展Python功能可以大大提高執(zhí)行速度,而Python自帶的ctypes庫(kù)由于在源生代碼上進(jìn)行封裝,執(zhí)行時(shí)間會(huì)高于源生代碼擴(kuò)展方式,但ctypes庫(kù)使用方便,特別適合應(yīng)用在第三方封裝代碼,提供動(dòng)態(tài)鏈接庫(kù)和調(diào)用文檔的場(chǎng)合。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41314.html
摘要:的批評(píng)者聲稱性能低效執(zhí)行緩慢,但實(shí)際上并非如此嘗試以下個(gè)小技巧,可以加快應(yīng)用程序。使用或者機(jī)器語(yǔ)言擴(kuò)展包來(lái)執(zhí)行關(guān)鍵任務(wù)能極大改善性能。但是如果你把求值的結(jié)果放入一個(gè)變量中,就能提高程序的性能。 Python是一門(mén)非??岬恼Z(yǔ)言,因?yàn)楹苌俚腜ython代碼可以在短時(shí)間內(nèi)做很多事情,并且,Python很容易就能支持多任務(wù)和多重處理。 Python的批評(píng)者聲稱Python性能低效、執(zhí)行緩慢,...
摘要:但是語(yǔ)言并沒(méi)有成功,究其原因,認(rèn)為是其非開(kāi)標(biāo)識(shí)放造成的。已經(jīng)成為最受歡迎的程序設(shè)計(jì)語(yǔ)言之一。年月,該語(yǔ)言作者在郵件列表上宣布將于年月日終止支持。其中很重要的一項(xiàng)就是的縮進(jìn)規(guī)則。設(shè)計(jì)定位的設(shè)計(jì)哲學(xué)是優(yōu)雅明確簡(jiǎn)單。 文本標(biāo)簽 換行標(biāo)簽 -- br 是單標(biāo)簽,意味著它沒(méi)有結(jié)束標(biāo)簽。起強(qiáng)制換行作用 段落中的文字段落中的文字段落中的文字 水平分割線 -- hr 與br相同,也是單標(biāo)簽??捎脕?lái)區(qū)分...
摘要:入門(mén),第一個(gè)這是一門(mén)很新的語(yǔ)言,年前后正式公布,算起來(lái)是比較年輕的編程語(yǔ)言了,更重要的是它是面向程序員的函數(shù)式編程語(yǔ)言,它的代碼運(yùn)行在之上。它通過(guò)編輯類工具,帶來(lái)了先進(jìn)的編輯體驗(yàn),增強(qiáng)了語(yǔ)言服務(wù)。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺(jué)已經(jīng)到來(lái)了,總結(jié)過(guò)去的 2017,相信小伙們一定有很多收獲...
摘要:入門(mén),第一個(gè)這是一門(mén)很新的語(yǔ)言,年前后正式公布,算起來(lái)是比較年輕的編程語(yǔ)言了,更重要的是它是面向程序員的函數(shù)式編程語(yǔ)言,它的代碼運(yùn)行在之上。它通過(guò)編輯類工具,帶來(lái)了先進(jìn)的編輯體驗(yàn),增強(qiáng)了語(yǔ)言服務(wù)。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺(jué)已經(jīng)到來(lái)了,總結(jié)過(guò)去的 2017,相信小伙們一定有很多收獲...
閱讀 2535·2023-04-25 14:54
閱讀 607·2021-11-24 09:39
閱讀 1815·2021-10-26 09:51
閱讀 3866·2021-08-21 14:10
閱讀 3493·2021-08-19 11:13
閱讀 2697·2019-08-30 14:23
閱讀 1813·2019-08-29 16:28
閱讀 3363·2019-08-23 13:45