python+C、C++混合編程的應用-創(chuàng)新互聯(lián)

TIOBE每個月都會新鮮出爐一份流行編程語言排行榜,這里會列出最流行的20種語言。排序說明不了語言的好壞,反應的不過是某個軟件開發(fā)領域的熱門程度。語言的發(fā)展不是越來越common,而是越來越專注領域。有的語言專注于簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。在有些領域,比如通信,性能很關鍵,但并不意味這個領域的coder只能苦苦掙扎于c/c++的陷阱中,比如可以使用多種語言混合編程。

成都創(chuàng)新互聯(lián)長期為上千多家客戶提供的網(wǎng)站建設服務,團隊從業(yè)經(jīng)驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為焦作企業(yè)提供專業(yè)的成都網(wǎng)站建設、成都網(wǎng)站制作,焦作網(wǎng)站改版等技術服務。擁有十載豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。

我看到的一個很好的Python與c/c++混合編程的應用是NS3(Network Simulator3)一款網(wǎng)絡模擬軟件,它的內部計算引擎需要用高性能,但在用戶建模部分需要靈活易用。NS3的選擇是使用C/C++來模擬核心部件和協(xié)議,用python來建模和擴展。

這篇文章介紹python和c/c++三種混合編程的方法,并對性能加以分析。

混合編程的原理

首先要說一下python只是一個語言規(guī)范,實際上python有很多實現(xiàn):CPython是標準Python,是由C編寫的,python腳本被編譯成CPython字節(jié)碼,然后由虛擬機解釋執(zhí)行,垃圾回收使用引用計數(shù),我們談與C/C++混合編程實際指的是基于CPython解釋上的。除此之外,還有Jython、IronPython、PyPy、Pyston,Jython是Java編寫的,使用JVM的垃圾回收,可以與Java混合編程,IronPython面向.NET平臺。
python與C/C++混合編程的本質是python調用C/C++編譯的動態(tài)鏈接庫,關鍵就是把python中的數(shù)據(jù)類型轉換成c/c++中的數(shù)據(jù)類型,給編譯函數(shù)處理,然后返回參數(shù)再轉換成python中的數(shù)據(jù)類型。

python中使用ctypes moduel,將python類型轉成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庫,使用下面的編譯指令

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

最后編寫python代碼,使用ctypes庫,將python類型轉換成c語言需要的類型,然后傳參調用so庫函數(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)   #調用so庫中的函數(shù)

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

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

  #include <Python.h>
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); //調用系統(tǒng)命令
        if (sts < 0) {
                PyErr_SetString(SpamError, "System command failed");
                return NULL;
        }
        return PyLong_FromLong(sts);     //將返回結果轉換為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)?、出參都作為PyObject對象來處理,然后使用轉換函數(shù)把python的數(shù)據(jù)類型轉換成c/c++中的類型,返回參數(shù)按相同方式處理。比第一種方法多了初始化函數(shù),這部分是把編譯的so庫當做python module所必需要做的。
python這樣使用:

imoprt spam
spam.system("ls")

使用c/c++編寫python擴展可以參見:http://docs.python.org/2.7/extending/extending.html

使用SWIG,來生成獨立的wrap文件

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

操作上,是針對c/c++程序編寫獨立的接口聲明文件(通常很簡單),swig會分析c/c++源程序自動分析接口要如何包裝。在指定目標語言后,swig會生成額外的包裝源碼文件。編譯so庫時,把包裝文件一起編譯、連接即可??磦€c代碼例子:

int system(const char* command)
{
        sts = system(command);
        if (sts < 0) {
                return NULL;
        }
        return sts;
}

c源碼中去掉適配python的包裝,僅定義system函數(shù)本身,這比第二種方式簡潔很多,并且剔除了c代碼與python的耦合代碼,是c代碼通用性更好。
然后編寫swig接口聲明文件spam.i:

%module spam
%{
#include "spam.h"
%}
%include "spam.h"
%include "typemaps.i"
int system(const char* INPUT);

這是一段語言無關的模塊聲明,要創(chuàng)建一個叫spam的模塊,對system做一個聲明,主要是聲明參數(shù)作為入?yún)⑹褂?。然后?zhí)行swig編譯程序:

>swig -c++ -python spam.i

swig會生成spam_wrap.cxx和spam.py兩個文件。先看spam_wrap.cxx,這個生成的文件很長,但關鍵的就是對函數(shù)的包裝:
python+C、C++混合編程的應用
包裝函數(shù)傳入的還是PyObejct對象,內部進行了類型轉換,最終調了源碼中的system函數(shù)。
生成的了另一個spam.py實際上是對so庫又用python包裝了一層(實際比較多余):
python+C、C++混合編程的應用

這里使用_spam模塊,這里實際上是把擴展命名為了_spam。關于swig在python上的應用可以參見:http://www.swig.org/Doc1.3/Python.html
下面就是編譯和安裝python 模塊,Python提供了distutils module,可以很方便的編譯安裝python的module。像下面這樣寫一個安裝腳本setup.py:

python+C、C++混合編程的應用

執(zhí)行 python setup.py build,即可以完成編譯,程序會創(chuàng)建一個build目錄,下面有編譯好的so庫。so庫放在當前目錄下,其實Python就可以通過import來加載模塊了。當然也可以用 python setup.py install 把模塊安裝到語言的擴展庫——site-packages目錄中。關于build python擴展,可以參考https://docs.python.org/2/extending/building.html#building

混合編程性能分析

混合編程的使用場景中,很重要一個就是性能攸關。那么這小節(jié)將通過幾個小實驗驗證下混合編程的性能如何,或者說怎樣寫程序能發(fā)揮好混合編程的性能優(yōu)勢。

我們使用冒泡排序算法來驗證性能。

1)實驗一 使用冒泡程序驗證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語言版冒泡排序

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;
    }
}
  使用一個長度為100內容固定的數(shù)組,反復排序10000次(每次排序后,再把數(shù)組恢復成原始序列),記錄執(zhí)行時間:
 在相同的機器上多次執(zhí)行,Python版執(zhí)行時間是10.3s左右,而c語言版本(未使用任何優(yōu)化編譯參數(shù))執(zhí)行時間只有0.29s左右。相比之下python的性能的確差很多(主要是python中l(wèi)ist的操作跟c的數(shù)組相比,效率差非常多),但python中很多擴展都是c語言寫的,目的就是為了提升效率,python用于數(shù)據(jù)分析的numpy庫就擁有不錯的性能。下個實驗就驗證,如果python使用c語言版本的冒泡排序擴展庫,性能會提升多少。

2)實驗二 python語言使用ctypes方式調用
這里直接使用c_int來定義了數(shù)組對象,這也節(jié)省了調用時數(shù)據(jù)類型轉換的開銷:

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萬次,結果c原生程序使用優(yōu)化參數(shù)編譯后用時0.65s左右。python使用c擴展后(相同編譯參數(shù))執(zhí)行僅需2.3s左右。
3)實驗三 在c語言中使用PyObject處理入?yún)?br/>這種方式是在python中依然使用list裝入待排序數(shù)列,在c函數(shù)中把list賦值給數(shù)組,再進行排序,排好序后,再對原始list賦值。循環(huán)排序10萬次,執(zhí)行用時1.0s左右。
4) 實驗四 使用swig來包裝c方法
在接口文件中聲明%array_class(int,intArray);然后在Python中使用initArray來作為數(shù)組,同樣修改成10萬次排序。python版本的程序(相同編譯參數(shù))執(zhí)行僅需0.7s左右,比c原生程序慢大概7%。

結論

1.python 的list效率非常低,在高性能場景下避免對list大量循環(huán)、取值、賦值操作。如需要最好使用ctype中的數(shù)組,或者是用c語言來實現(xiàn)。
2.應該把耗時的cpu密集型的邏輯交給c/c++實現(xiàn),python使用擴展即可。

另外有需要云服務器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。

分享文章:python+C、C++混合編程的應用-創(chuàng)新互聯(lián)
鏈接分享:http://bm7419.com/article48/cecjhp.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供電子商務企業(yè)網(wǎng)站制作、商城網(wǎng)站靜態(tài)網(wǎng)站、定制開發(fā)、全網(wǎng)營銷推廣

廣告

聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

外貿網(wǎng)站制作