python與C 聯合編譯

Python與C++聯合編程的簡介


類型:Python,創建時間:三月 22, 2013, 8:47 p.m.

標題無“轉載”即原創文章,版權所有。轉載請註明來源:http://hgoldfish.com/blogs/article/87/。

作爲Python程序員,應該能夠正視Python的優點與缺點。衆所周之,Python的運行速度是很慢的,特別是大數據量的運算時,Python會慢得讓人難以忍受。對於這種情況,“專業”的解決方案是用上numpy或者opencl。不過有時候爲了一點小功能用上這種重型的解決方案很不划算,或者有時候想要實現的操作在numpy裏面沒有,需要我們自己用C語言來編寫。總之,我們使用Python與C++的混合編程能夠加快程序熱點的運算速度。

首先要提醒大家注意的是,在考慮聯合編程之前一定要找到程序運行的熱點。簡單一點地,使用標準庫的profile或者cProfile模塊找到最消耗CPU的位置,如果這個位置只簡單的消耗IO時間,通常換成C++程序的意義也不會很大,此時做聯合編程可能是事倍功半,起不到多大的效果。

還有些情況,Python程序員們想要使用操作系統或者外部模塊提供的函數。這些模塊一般是爲C/C++程序員提供的。這時候也是Python與C++聯合編程的用武之地。

Python語言可以說是最好的膠水語言。僅就與C++聯合編程這個問題來講,依使用難度與功能來排列,Python社區提供了以下幾種解決方案:

  1. 使用標準庫ctypes直接調用C/C++編寫的動態鏈接庫。這是最簡單易用的方案。C/C++程序員使用自己的豐富的經驗,把預定的功能實現爲動態鏈接庫。而Python程序員只要知道這些動態鏈接庫函數的名稱、參數類型與返回值類型就能簡單地調用它。當你傳入參數時,ctypes模塊會自動地把Python的對象成爲C/C++所對應的參數類型。比如以下調用Windows的API:

    #定義參數類型與函數名稱
    from ctypes.wintypes import UINT, DWORD
    GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
    class LASTINPUTINFO(ctypes.Structure):
        _fields_ = [("cbSize", UINT),
                  ("dwTime", DWORD)]
    
    #開始調用DLL導出的函數
    def getLastInputTime_nt():
        info = LASTINPUTINFO()
        info.cbSize = ctypes.sizeof(info)
        info.dwTime = 0
        if not GetLastInputInfo(ctypes.byref(info)):
            raise WindowsError("")
        return info.dwTime
    

    在這裏展示瞭如何構造Windows的API所需要的結構體,如何填充結構體並分析返回值。

    ctypes還能將Python函數提供給C/C++代碼作爲回調函數。

    與其它解決方案相比。ctypes不需要程序員熟悉C/C++語言,不需要安裝一個C/C++的編譯器,它通過操作系統的接口直接操作C/C++代碼。而且ctypes是標準庫的一部分,只要安裝了Python就可以直接使用。這幾個原因使得它深受Python程序員的喜愛。

    而它的劣勢呢。首先,ctypes不能簡單調用C++程序,因爲C++在編譯的時候使用了name mangling這個技術來實現函數的重載。C++會自動地爲類的成員函數加上類名前綴。所以,C++程序員需要以C語言的調用約定來提供接口,沒有類,沒有重載函數,沒有模板,沒有C++異常。不能直接調用現有的C++代碼可能是這個方案最大的缺點。

    另外,對於list, set之類的數據類型,ctypes不能識別並自動地在Python與C/C++數據類型之間轉換。C/C++部分不能識別Python數據類型,這時候只能用Python語言來編寫轉換代碼。如果數據量較大,或者調用很頻繁,轉換代碼反而會浪費很多的資源。這或許是ctypes的另一個劣勢之一了。

  2. 如果你使用的是Jython或者IronPython的話,它們也提供了類似於ctypes之類的模塊,能夠直接訪問Java或者.Net語言編寫的模塊。其優勢與劣勢大致與ctypes相似。因爲其使用範圍有限,這裏不再詳述。

  3. 使用Cython語言,一種類似於Python語言的一種新型語言編寫預定功能的代碼,然後將這些代碼轉換成爲C語言編譯成爲Python語言可以直接調用的二進制模塊。Cython語言是融合Python語言與C語言的一種新型語言。它本身能夠理解Python語言的語法,然後在其基礎上增加了某些C語言的語法,以便更精細地控制數據類型與指針。基本兼容Python語法是這個解決方案最大的特點。很多時候,Python程序員只要在舊的代碼中簡單地聲明一下代碼中所使用的參數、變量的類型,就能把立即爲舊的Python程序提速。

    Cython提供了一個名爲pyximporter的工具,能夠在安裝了C/C++編譯器的計算機上面爲簡單的Cython程序直接生成相應的Python模塊。這使得Cython的使用與普通的Python程序一樣簡單。比如下面這段代碼,直接保存爲myhello.pyx即可被調用。

    #myhello.pyx
    def sayHelloTenTimes():
        cdef int i #只要簡單地爲變量標識類型即可加速循環。
        for i in range(0, 10):
            print("hello, world!")
    
    $ python
    >>> import pyximport; pyximport.install()
    >>> import myhello
    >>> myhello.sayHelloTenTimes()
    

    由此可見,Cython非常容易使用。而且不僅能夠處理C語言的模塊,還能處理C++的模塊——雖然沒有直接支持虛函數之類的完整C++特性。因爲它不直接使用C/C++語法,而是另外設計比C/C++更簡潔優雅的新型語法,因此,對於不熟悉C/C++的程序員來說有很大的吸引力。相比ctypes來說,因爲參數類型轉換更加智能與高效,所以通常能夠提升更多的效率。

    劣勢呢,所謂用Python程序員所熟練的語法來編寫高速的運算代碼,乍一聽相當地有吸引力。但是如果想要更深入地控制內存與數據結構時,程序員可能會發現,現在他不得不熟練地掌握C/C++語言,然後用Cython的語法寫出來。以程序員們懶惰的性格,這反而是件難以忍受的事件。這或許是Cython本身並不大流行的主要原因吧。

  4. 使用boost.python。有意思的是,與ctypes/Cython形成鮮明的對比,boost.python傾向於讓C++程序員擁有更熟悉的編程環境。它讓C++程序員使用他所熟悉的C++語法直接控制Python的數據結構,調用Python的解釋器。它沒有像Cython那樣發明新的語法,而是直接使用C++的語法,編寫供Python使用的接口。與Cython同樣的道理,它的效率優勝於ctypes。

    與Cython/SWIG/SIP等方案相比,程序員只需要學習C/C++與Python兩種語言。另外,與本文提到的幾種解決方案相比,它非常適合在主要由C++編寫的程序中控制Python代碼。不僅功能更強大、效率還更高。如此神奇的解決方案會有什麼劣勢呢?某些人可能不同意吧,老魚一聽說它依賴於boost就蔫了,感覺編譯與學習龐大又奇怪的boost非常浪費生命。

  5. 使用SWIG或者SIP,通過編寫一個接口文件,使用類似於C/C++語法——聲明函數、類型的信息,然後使用特殊的工具爲C/C++的代碼生成Python的接口代碼。這些接口代碼能夠在Python與C/C++之間的數據結構轉換。最終編譯這些接口代碼,成爲Python的二進制模塊。SWIG與SIP的接口文件與C/C++的頭文件非常相似。

    這兩種工具差不多,因爲。本質上,他們都與Cython類似,都使用了中間語言來生成轉換代碼。但SWIG/SIP能夠在他們的接口文件中嵌入C/C++,能夠讓程序員仔細地調節數據類型的轉換過程。在使用上,它比Cython的層次更低,更接近於Python本身提供的API。

    SWIG能夠爲多種腳本語言生成轉換代碼。而SIP則專門針對Python與C++。此外,SIP本身是作爲PyQt的專門工具來開發的,因此它能夠理解Qt的signal/slot。從應用項目上來看,SWIG似乎會更廣泛一點。而SIP,目前所見的項目基本都與PyQt相關。據說SWIG對於C++的支持不好,不知道有沒有人來說一下呢。相比之下,SIP對於C++的支持非常完善,諸如虛函數、protected member function、模版、析構函數、異常等特性都得到良好的支持。而且SIP支持Python的GIL,還擁有一個使用Python編寫的編譯系統。可能會更方便一點。

    然而這種方案畢竟要學習一種新的語言,所以從表面上來看不如Cython和boost.python討喜。當程序員想要仔細地調節類型轉換代碼的時候,需要學習SWIG/SIP的內部機制,被限定使用特殊的變量名。這使得這種方案的學習曲線相對較高。

  6. 直接使用Python的API,可以稱之爲最終解決方案。Cython, SWIG, SIP的接口文件轉換後所生成的C/C++代碼實際上都使用Python的API。與其它方案相比,這種方案相當地繁複,必須爲每次函數調用編寫數據轉換代碼,還要操心Python對象的引用計數。我覺得這種方案一無是處,這時就不再多講了。其它的工具pybindgen不知道什麼情況。有興趣的話可以看看。

好了。題外話一句吧,我一直覺得ctypes與xmlrpc並列Python語言的兩大神器,最能體現Python的生產效率。

希望本文在大家選擇一種技術路線時能提供一點點幫助。

標題無“轉載”即原創文章,版權所有。轉載請註明來源:http://hgoldfish.com/blogs/article/87/。


cooper(四月 1, 2013, 1:45 p.m.) 

cffi 模塊調用c代碼比ctypes要更方便。 https://bitbucket.org/cffi/cffi

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章