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