Python懦尝,C語(yǔ)言,C++混合編程的應(yīng)用壤圃,帶你起飛!

TIOBE每個(gè)月都會(huì)新鮮出爐一份流行編程語(yǔ)言排行榜琅轧,這里會(huì)列出最流行的20種語(yǔ)言伍绳。排序說(shuō)明不了語(yǔ)言的好壞,反應(yīng)的不過(guò)是某個(gè)軟件開(kāi)發(fā)領(lǐng)域的熱門程度乍桂。語(yǔ)言的發(fā)展不是越來(lái)越common冲杀,而是越來(lái)越專注領(lǐng)域。有的語(yǔ)言專注于簡(jiǎn)單高效睹酌,比如Python权谁,內(nèi)建的list,dict結(jié)構(gòu)比c/c++易用太多憋沿,但同樣為了安全旺芽、易用,語(yǔ)言也犧牲了部分性能辐啄。在有些領(lǐng)域采章,比如通信,性能很關(guān)鍵壶辜,但并不意味這個(gè)領(lǐng)域的coder只能苦苦掙扎于c/c++的陷阱中悯舟,比如可以使用多種語(yǔ)言混合編程。

我看到的一個(gè)很好的Python與c/c++混合編程的應(yīng)用是NS3(Network Simulator3)一款網(wǎng)絡(luò)模擬軟件砸民,它的內(nèi)部計(jì)算引擎需要用高性能抵怎,但在用戶建模部分需要靈活易用奋救。NS3的選擇是使用C/C++來(lái)模擬核心部件和協(xié)議,用Python來(lái)建模和擴(kuò)展反惕。

這篇文章介紹Python和c/c++三種混合編程的方法尝艘,并對(duì)性能加以分析。

混合編程的原理

首先要說(shuō)一下Python只是一個(gè)語(yǔ)言規(guī)范承璃,實(shí)際上Python有很多實(shí)現(xiàn):CPython是標(biāo)準(zhǔn)Python利耍,是由C編寫的,python腳本被編譯成CPython字節(jié)碼盔粹,然后由虛擬機(jī)解釋執(zhí)行隘梨,垃圾回收使用引用計(jì)數(shù),我們談與C/C++混合編程實(shí)際指的是基于CPython解釋上的舷嗡。除此之外轴猎,還有Jython、IronPython进萄、PyPy捻脖、Pyston,Jython是Java編寫的中鼠,使用JVM的垃圾回收可婶,可以與Java混合編程,IronPython面向.NET平臺(tái)援雇。

Python與C/C++混合編程的本質(zhì)是python調(diào)用C/C++編譯的動(dòng)態(tài)鏈接庫(kù)矛渴,關(guān)鍵就是把Python中的數(shù)據(jù)類型轉(zhuǎn)換成c/c++中的數(shù)據(jù)類型,給編譯函數(shù)處理惫搏,然后返回參數(shù)再轉(zhuǎn)換成Python中的數(shù)據(jù)類型具温。

Python中使用ctypes moduel,將Python類型轉(zhuǎn)成c/c++類型

首先筐赔,編寫一段累加數(shù)值的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庫(kù),使用下面的編譯指令

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

最后編寫python代碼茴丰,使用ctypes庫(kù)达皿,將python類型轉(zhuǎn)換成c語(yǔ)言需要的類型,然后傳參調(diào)用so庫(kù)函數(shù):

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) #調(diào)用so庫(kù)中的函數(shù)

在C/C++程序中使用Python.h较沪,寫wrap包裝接口

這種方法需要修改c/c++代碼鳞绕,在外部函數(shù)中處理入/出參,適配python的參數(shù)尸曼。寫一段c代碼將外部入?yún)⒆鳛閟hell命令執(zhí)行:

#include

static PyObject* SpamError;

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

{

const char* command;

int sts;

if (!PyArg_ParseTuple(args, "s", &command)) //將args參數(shù)按照string類型處理们何,給command賦值

return NULL;

sts = system(command); //調(diào)用系統(tǒng)命令

if (sts < 0) {

PyErr_SetString(SpamError, "System command failed");

return NULL;

}

return PyLong_FromLong(sts); //將返回結(jié)果轉(zhuǎn)換為PyObject類型

}

//方法表

static PyMethodDef SpamMethods[] = {

{"system", spam_system, METH_VARARGS,

"Execute a shell command."},

{NULL, NULL, 0, NULL}

};

//模塊初始化函數(shù)

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);

}

處理上所有的入?yún)ⅰ⒊鰠⒍甲鳛镻yObject對(duì)象來(lái)處理控轿,然后使用轉(zhuǎn)換函數(shù)把Python的數(shù)據(jù)類型轉(zhuǎn)換成c/c++中的類型冤竹,返回參數(shù)按相同方式處理拂封。比第一種方法多了初始化函數(shù),這部分是把編譯的so庫(kù)當(dāng)做Pythonmodule所必需要做的鹦蠕。

Python這樣使用:

imoprt spam

spam.system("ls")

使用c/c++編寫python擴(kuò)展可以參見(jiàn):http://docs.python.org/2.7/extending/extending.html

使用SWIG冒签,來(lái)生成獨(dú)立的wrap文件

這種方式并不能算是一種新方式,實(shí)際上是基于第二中方式的一種包裝钟病。SWIG是個(gè)幫助使用C或者C++編寫的軟件能與其它各種高級(jí)編程語(yǔ)言進(jìn)行嵌入聯(lián)接的開(kāi)發(fā)工具萧恕。SWIG能應(yīng)用于各種不同類型的語(yǔ)言包括常用腳本編譯語(yǔ)言例如Perl, PHP, Python, Tcl, Ruby, PHP,C#,Java,R等肠阱。

操作上票唆,是針對(duì)c/c++程序編寫?yīng)毩⒌慕涌诼暶魑募ㄍǔ:芎?jiǎn)單),swig會(huì)分析c/c++源程序自動(dòng)分析接口要如何包裝屹徘。在指定目標(biāo)語(yǔ)言后走趋,swig會(huì)生成額外的包裝源碼文件。編譯so庫(kù)時(shí)噪伊,把包裝文件一起編譯簿煌、連接即可〖担看個(gè)c代碼例子:

int system(const char* command)

{

sts = system(command);

if (sts < 0) {

return NULL;

}

return sts;

}

c源碼中去掉適配Python的包裝姨伟,僅定義system函數(shù)本身,這比第二種方式簡(jiǎn)潔很多豆励,并且剔除了c代碼與Python的耦合代碼授滓,是c代碼通用性更好。

然后編寫swig接口聲明文件spam.i:

%module spam

%{

#include "spam.h"

%}

%include "spam.h"

%include "typemaps.i"

int system(const char* INPUT);

這是一段語(yǔ)言無(wú)關(guān)的模塊聲明肆糕,要?jiǎng)?chuàng)建一個(gè)叫spam的模塊,對(duì)system做一個(gè)聲明在孝,主要是聲明參數(shù)作為入?yún)⑹褂贸峡小H缓髨?zhí)行swig編譯程序:

>swig -c++ -python spam.i

swig會(huì)生成spam_wrap.cxx和spam.py兩個(gè)文件。先看spam_wrap.cxx私沮,這個(gè)生成的文件很長(zhǎng)始赎,但關(guān)鍵的就是對(duì)函數(shù)的包裝:

包裝函數(shù)傳入的還是PyObejct對(duì)象,內(nèi)部進(jìn)行了類型轉(zhuǎn)換仔燕,最終調(diào)了源碼中的system函數(shù)造垛。

生成的了另一個(gè)spam.py實(shí)際上是對(duì)so庫(kù)又用Python包裝了一層(實(shí)際比較多余):

這里使用_spam模塊,這里實(shí)際上是把擴(kuò)展命名為了_spam晰搀。關(guān)于swig在python上的應(yīng)用可以參見(jiàn):http://www.swig.org/Doc1.3/Python.html

下面就是編譯和安裝python 模塊五辽,Python提供了distutils module,可以很方便的編譯安裝python的module外恕。像下面這樣寫一個(gè)安裝腳本setup.py:

![](http://i2.51cto.com/images/blog/201810/07/6f294d1352d038e7cd1904283d5c5634.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

執(zhí)行 python setup.py build杆逗,即可以完成編譯乡翅,程序會(huì)創(chuàng)建一個(gè)build目錄,下面有編譯好的so庫(kù)罪郊。so庫(kù)放在當(dāng)前目錄下蠕蚜,其實(shí)Python就可以通過(guò)import來(lái)加載模塊了。當(dāng)然也可以用 python setup.py install 把模塊安裝到語(yǔ)言的擴(kuò)展庫(kù)——site-packages目錄中悔橄。關(guān)于build python擴(kuò)展靶累,可以參考https://docs.python.org/2/extending/building.html#building

混合編程性能分析

混合編程的使用場(chǎng)景中,很重要一個(gè)就是性能攸關(guān)癣疟。那么這小節(jié)將通過(guò)幾個(gè)小實(shí)驗(yàn)驗(yàn)證下混合編程的性能如何挣柬,或者說(shuō)怎樣寫程序能發(fā)揮好混合編程的性能優(yōu)勢(shì)。

我們使用冒泡排序算法來(lái)驗(yàn)證性能争舞。

1)實(shí)驗(yàn)一 使用冒泡程序驗(yàn)證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語(yǔ)言版冒泡排序

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;

}

}

使用一個(gè)長(zhǎng)度為100內(nèi)容固定的數(shù)組凛忿,反復(fù)排序10000次(每次排序后,再把數(shù)組恢復(fù)成原始序列)竞川,記錄執(zhí)行時(shí)間:

在相同的機(jī)器上多次執(zhí)行店溢,Python版執(zhí)行時(shí)間是10.3s左右,而c語(yǔ)言版本(未使用任何優(yōu)化編譯參數(shù))執(zhí)行時(shí)間只有0.29s左右委乌。相比之下Python的性能的確差很多(主要是Python中l(wèi)ist的操作跟c的數(shù)組相比床牧,效率差非常多),但Python中很多擴(kuò)展都是c語(yǔ)言寫的遭贸,目的就是為了提升效率戈咳,Python用于數(shù)據(jù)分析的numpy庫(kù)就擁有不錯(cuò)的性能。下個(gè)實(shí)驗(yàn)就驗(yàn)證壕吹,如果Python使用c語(yǔ)言版本的冒泡排序擴(kuò)展庫(kù)著蛙,性能會(huì)提升多少。

2)實(shí)驗(yàn)二 Python語(yǔ)言使用ctypes方式調(diào)用

這里直接使用c_int來(lái)定義了數(shù)組對(duì)象耳贬,這也節(jié)省了調(diào)用時(shí)數(shù)據(jù)類型轉(zhuǎn)換的開(kāi)銷:

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

再次執(zhí)行:

為了減少誤差踏堡,把循環(huán)增加到10萬(wàn)次,結(jié)果c原生程序使用優(yōu)化參數(shù)編譯后用時(shí)0.65s左右咒劲。Python使用c擴(kuò)展后(相同編譯參數(shù))執(zhí)行僅需2.3s左右顷蟆。

3)實(shí)驗(yàn)三 在c語(yǔ)言中使用PyObject處理入?yún)?/p>

這種方式是在Python中依然使用list裝入待排序數(shù)列,在c函數(shù)中把list賦值給數(shù)組腐魂,再進(jìn)行排序帐偎,排好序后,再對(duì)原始list賦值蛔屹。循環(huán)排序10萬(wàn)次削樊,執(zhí)行用時(shí)1.0s左右。

4) 實(shí)驗(yàn)四 使用swig來(lái)包裝c方法

在接口文件中聲明%array_class(int,intArray);然后在Python中使用initArray來(lái)作為數(shù)組兔毒,同樣修改成10萬(wàn)次排序嫉父。Python版本的程序(相同編譯參數(shù))執(zhí)行僅需0.7s左右沛硅,比c原生程序慢大概7%。

結(jié)論

1.Python的list效率非常低绕辖,在高性能場(chǎng)景下避免對(duì)list大量循環(huán)摇肌、取值、賦值操作仪际。如需要最好使用ctype中的數(shù)組围小,或者是用c語(yǔ)言來(lái)實(shí)現(xiàn)。

2.應(yīng)該把耗時(shí)的cpu密集型的邏輯交給c/c++實(shí)現(xiàn)树碱,Python使用擴(kuò)展即可肯适。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市成榜,隨后出現(xiàn)的幾起案子框舔,更是在濱河造成了極大的恐慌,老刑警劉巖赎婚,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刘绣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挣输,警方通過(guò)查閱死者的電腦和手機(jī)纬凤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撩嚼,“玉大人停士,你說(shuō)我怎么就攤上這事⊥昀觯” “怎么了恋技?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逻族。 經(jīng)常有香客問(wèn)我猖任,道長(zhǎng),這世上最難降的妖魔是什么瓷耙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮刁赖,結(jié)果婚禮上搁痛,老公的妹妹穿的比我還像新娘。我一直安慰自己宇弛,他們只是感情好鸡典,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著枪芒,像睡著了一般彻况。 火紅的嫁衣襯著肌膚如雪谁尸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天纽甘,我揣著相機(jī)與錄音良蛮,去河邊找鬼。 笑死悍赢,一個(gè)胖子當(dāng)著我的面吹牛决瞳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播左权,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼皮胡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了赏迟?” 一聲冷哼從身側(cè)響起屡贺,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锌杀,沒(méi)想到半個(gè)月后甩栈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抛丽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年谤职,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亿鲜。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡允蜈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒿柳,到底是詐尸還是另有隱情饶套,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布垒探,位于F島的核電站妓蛮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圾叼。R本人自食惡果不足惜蛤克,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夷蚊。 院中可真熱鬧构挤,春花似錦、人聲如沸惕鼓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至矾飞,卻和暖如春一膨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洒沦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工豹绪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人微谓。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓森篷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親豺型。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仲智,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容