python调用c++_python高性能编程之Cython篇 第一章
第一節 cython的潛能
?Cython是一種編程語言,它將Python與C和C ++的靜態類型系統相結合。
?Cython是一個將Cython源代碼轉換為高效的C或C ++源代碼的編譯器。然后可以將此源代碼編譯為Python擴展模塊或獨立可執行文件。
Cython的強大功能來自它結合了Python和C的方式:感覺就像Python一樣,同時提供了對C語言的輕松訪問.Cython位于高級Python和低級C之間;有人可能會稱它為克里奧爾語編程語言。但是Python和類C語言是如此不同 - 為什么要將它們結合起來?正是因為他們的差異是互補的。 Python具有高級,動態,易學和靈活的特點。然而,這些積極因素帶來了成本 - 因為Python是動態的并且是解釋的,它可能比靜態類型編譯慢幾個數量級。另一方面,C是廣泛使用的最古老的靜態類型編譯語言之一,因此編譯器已經有近半個世紀的時間來優化其性能。 C非常低級且非常強大。與Python不同,它沒有很多安全措施,并且很難使用。
兩種語言都是主流,但鑒于它們的不同,它們通常用于不同的領域。 Cython的美妙之處在于:它結合了Python的表現力和C的強勁的性能,同時寫起來仍然感覺像Python。除了極少數例外,Python代碼(版本2.x和3.x)已經是有效的Cython代碼。 Cython在Python語言中添加了少量關鍵字,以利用C的類型,允許cython編譯器生成有效的C代碼。如果您已經了解Python并且對C或C ++有基本的了解,那么您將能夠快速學習Cython。您不必學習另一種新的語言。我們可以將Cython視為一個介于python和c或c++中間的項目。如果將Python編譯為C語言是Cython的反面,那么將C或C ++與Python接口就是它的正面。我們可以從需要更好性能的Python代碼開始,或者我們可以從需要優化Python接口的C(或C ++)代碼開始。為了加速Python代碼,Cython使用可選的靜態類型聲明編譯Python源代碼,以根據算法實現大規模的性能改進。要使用Python連接C或C ++庫,我們可以使用Cython與外部代碼進行交互并創建優化的包裝器。這兩種功能 - 編譯Python和與外部代碼接口 - 都可以很好地協同工作,每個功能都是Cython有用的重要組成部分。
(補充:
Cython和CPython的區別
Cython經常與CPython混淆(請注意P),但兩者是非常不同的。
CPython是標準和最廣泛使用的Python實現的名稱。
CPython的核心是用C語言編寫的,CPython中的C用于區別于Python語言規范和其他語言的Python實現,例如Jython(Java),IronPython(.NET)和PyPy(Python中實現的))。 CPython為Python語言提供了一個C級接口;該接口稱為Python / C API。 Cython廣泛使用這個C接口,因此Cython依賴于CPython。 Cython不是Python的另一個實現 - 它需要CPython運行時來運行它生成的擴展模塊。
)
我們來看一個例子吧。
第二節 比較Python,C和Cython
考慮一個簡單的Python函數fib,它計算第n個Fibonacci數:1
def fib(n):a,b=0.0,1.0for i in range(n):a,b=a+b,areturn a正如在介紹中提到的,這個Python函數已經是一個有效的Cython函數,在Python和Cython中它具有相同的行為。 我們很快就會看到我們可以為fib添加基于Cython的特定語法來提高其性能。
下面是純C語言的實現:
double cfib(int n){int i;double a=0.0,b=1.0,tmp;for(i=0;i<n;i++){tmp=a;a=a+b;b=tmp;}return a; }
我們在C版本中使用double并在Python版本中以float進行比較直接并刪除與C數據類型的整數溢出相關的任何問題。想象將C版本中的類型與Python版本中的代碼混合。結果是一個靜態類型的Cython版本:
如前所述,Cython和Python代碼非常類似,因此我們未經修改的Python fib函數也是有效的Cython代碼。轉換動態類型的Python版本 為靜態類型的Cython版本,我們使用cdef 的Cython語句來聲明靜態類型的C變量i,a和b。即使對于之前沒有看過Cython代碼的讀者,也是非常直觀好理解的。具體的性能比較見下表:
注意,這里分別進行了0和90的斐波那契比較,可以看出fib(90)的計算量下cython,純c以及c擴展的性能非常接近基本一樣在表1-1中,第二列的測量fib(0)和第三列的運行時間
測量是其它三種方式相對于Python的fib(0)的加速。因為fib的參數控制循環迭代的次數,所以fib(0)不會進入Fibonacci循環,因此它的運行時間是語言運行時和函數調用開銷的合理度量。(emmm。。。實際上在工業級項目上這一點非計算型的開銷 時間還是可以接受的)
第四和第五列測量fib(90)的運行時和加速,其執行循環90次。調用開銷和循環執行運行時都有助于其運行時。第六和第七列測量fib(90)運行時和fib(0)運行時之間的差異以及相對加速。這種差異僅僅是循環運行時的近似值,消除了運行時和調用開銷。可以看到純C,C extension以及cython的表現非常類似。
Table 1-1有四行:
純Python
第一行(在標題之后)測量純Python版本的fib的性能,并且如預期的那樣,在所有類別中,它具有顯著的最差性能。特別是,fib(0)的調用開銷在該系統上超過半微秒。 fib(90)中的每個循環迭代需要將近150納秒; Python留下了很大的改進空間。
純C.
第二行測量純C版本的fib的性能。在這個版本中,沒有與Python運行時的交互,因此最小化 了調用開銷;這也意味著它無法從Python中使用。這個版本提供了一個我們可以合理地期望從簡單的序列fib函數中獲得最佳性能。與Python相比,fib(0)值表示C函數調用開銷最小(2納秒),并且fib(90)運行時(164納秒)比此特定系統上的Python快近80倍。
手寫C擴展
第三行測量Python 2的手寫C擴展模塊
擴展模塊需要幾十行C代碼,其中大部分都是調用Python / C API的樣板。從Python調用時,擴展模塊必須將Python對象轉換為C數據,計算C中的Fibonacci數,然后進行轉換結果返回Python對象。它的調用開銷(fib(0)列)相應地大于純C版本,后者不必從Python對象轉換。因為它是用C語言編寫的,所以對于fib(0),它比純Python快三倍。它還為fib(90)提供了一個很好的30倍加速。(https://blog.csdn.net/fitzzhang/article/details/79212411 找到一篇關于c-extension的介紹, 對于python中的C extension并沒有太多研究,大概的理解就是用C來寫后端的邏輯然后轉化為dll這類文件再用python導入)
大概長這樣,之后還要進行編譯和擴展,具體的可以參考上面的鏈接里的介紹
#include <Python.h>static PyObject *module_func(PyObject *self, PyObject *args) {/* Do your stuff here. */Py_RETURN_NONE; }static PyMethodDef module_methods[] = {{ "func", (PyCFunction)module_func, METH_VARARGS, NULL },{ NULL, NULL, 0, NULL } };PyMODINIT_FUNC initModule() {Py_InitModule3(Module, module_methods, "docstring..."); } --------------------- 作者:fitzzhang 來源:CSDN 原文:https://blog.csdn.net/fitzzhang/article/details/79212411 版權聲明:本文為博主原創文章,轉載請附上博文鏈接!這里有一個問題就是,使用這種c-extension的方式會造成數據轉化的開銷,就是調用c-extension寫好的邏輯的實現時,需要將python的數據類型轉化為c的數據類型然后在c中進行運算,最后把計算的結果再由c的數據類型轉化為python的數據類型。
我在使用julia寫后端邏輯然后通過pyjulia導入的用julia寫的邏輯函數的時候,沒有直接用純julia實現相同的功能來得快,就是在這個數據轉化上的開銷。這一塊的話,打算還是放到julia的那個專欄里寫好了,展開說太多了麻煩死了。
但是沒有辦法,目前公司業務上使用到的代碼中python占了很大一部分,如果要完全替換成julia或者純C勢必會非常非常麻煩,而且雖然python速度相對慢,但是在大部分的場景下,通過調用多進程的方式可以把時間降低到可接受范圍內,只不過可能有一些核心的功能,有一些特殊的時間上的要求,這個時候通過cython、c或c++、julia等等這類高性能的解決方案來解決會顯得比較方便而優雅,而且還能多掌握一門使用(裝逼)技術。總的來說,可以把python比喻成沒有拳套的滅霸,cython、c++等等這些算是無限寶石了。
Cython
最后一行測量Cython版本的性能。與C擴展一樣,它可以從Python中使用,因此它必須先將Python對象轉換為C數據,然后才能計算Fibonacci數,然后將結果轉換回Python。由于這種開銷,它無法與fib(0)的純C版本匹配,但值得注意的是,它的開銷比手寫C擴展的開銷快2.5倍。由于這種降低了的調用開銷,它能夠為純粹的Python(90)提供大約50倍的加速。
table1-1中的內容是最后兩列:純粹的循環運行時C,C擴展和Cython版本在這個系統上都是165納秒,并且相對于Python的加速比都大約是75倍。
Cython通常可以生成與純C等效的高效代碼。因此,在正確計算Python開銷時,我們看到Cython達到了C級性能。 而且,它比手寫的C擴展模塊更好,在Python-to-C轉換上比C擴展模塊的開銷更小這是核心原因。
Cython生成高度優化的代碼,通常比一個等效的手寫C擴展模塊更快 。它經常能夠 生成Python-to-C轉換代碼,這是比使用Python / C API的原始調用快的幾個因素之一。
正如我們將在第3章中學到的,我們可以更進一步,使用Cython來創建沒有Python開銷的Python類C函數。但Cython代碼不能直接從Python調用。它們允許我們為核心計算移除昂貴的調用開銷。
Cython的性能改進是什么原因?對于此示例,可能的原因是函數調用開銷,循環,數學運算以及堆棧與堆分配。
(1)函數調用的開銷
fib(0)運行的時間主要是由相應語言調用函數所花費的時間來決定的,我們在表1-1中看到,Cython生成的代碼比調用Python函數快了近一個數量級,比手寫C擴展快兩倍多。 Cython通過傳遞一些較慢的Python / C API調用來生成高度優化的C代碼來實現這一點。我們在前面的C擴展時序中使用這些API調用。
(2)循環
與編譯語言相比,Python for循環非常慢。加速循環Python代碼的一種可靠方法是找到將Python for和while循環移動到編譯代碼中,方法是調用內置函數或使用Cython之類的東西為您進行轉換。通過表中的fib(90)列以每種語言運行for循環90次迭代的結果我們可以很明顯的感受到循環對于時間效率上的影響。(numba實際上也可以針對python的循環進行優化不過沒有系統進行比較過,numba的tutorial在github上面也有,有空也寫一寫)
(3)數學運算
因為Python是動態類型的,不能進行任何基于類型的優化,所以像 a+ b 這樣的表達式可以做任何事情。我們可能知道a和b只會成為浮點數,但Python從未做過這種假設。所以,在運行時,Python必須查找a和b的類型(在本例中,它們是相同的)。然后它必須找到類型的底層__add__方法(或等效方法),并使用a和b作為參數調用__add__。在這個過程中,必須先將Python的浮點數a和b的轉化為底層C的double數據類型,只有這樣,才能進行實際的添加!此添加的結果必須打包在 全新的Python浮點并作為結果返回。
(cpython,注意是cpython不是cython,這里補充一下基礎知識:
CPython:是用C語言實現Pyhon,是目前應用最廣泛的解釋器。最新的語言特性都是在這個上面先實現,基本包含了所有第三方庫支持,但是CPython有幾個缺陷,一是全局鎖使Python在多線程效能上表現不佳,二是CPython無法支持JIT(即時編譯),導致其執行速度不及Java和Javascipt等語言。于是出現了Pypy。。
Python的解釋器:
由于Python是動態編譯的語言,和C/C++、Java或者Kotlin等靜態語言不同,它是在運行時一句一句代碼地邊編譯邊執行的,而Java是提前將高級語言編譯成了JVM字節碼,運行時直接通過JVM和機器打交道,所以進行密集計算時運行速度遠高于動態編譯語言。
)
C和Cython版本已經知道a和b是double數據類型的,所以對a和b進行add操作只需要編譯一個機器代碼指令。
(4)堆棧與堆分配
在C的層面上,動態Python對象完全是堆分配的。 Python非常努力地使用內存池智能地管理內存,并內化常用的整數和字符串。但事實仍然是創造和摧毀對象- 任何對象,甚至是標量 - 都會產生動態分配內存和Python內存子系統的開銷。由于Python浮點對象是不可變的,因此使用Python浮點數的操作涉及堆分配對象的創建和銷毀。 Fib的Cython版本將所有變量聲明為堆棧分配的C的雙精度數。
通常,堆棧分配比堆分配快得多。此外,C浮點數是可變的,這意味著for循環體在分配和內存使用方面更有效。因為Python循環體必須在每次迭代中完成更多的工作,所以C和Cython版本比純Python快一個數量級并不奇怪。(這一點其實我不是很理解,有理解的大佬求評論調教)
潑一盆冷水
當我們向Python代碼添加一些簡單的cdef語句時,看到大量的性能改進是令人振奮的。但是,值得注意的是,在使用Cython編譯時,并非所有Python代碼都會看到大量的性能改進。前面的fib示例是故意CPU綁定的,這意味著所有運行時間都花在操作CPU寄存器內的一些變量上,幾乎不需要數據移動。相反,如果此函數是內存綁定(例如,添加兩個大型數組的元素),I / O綁定(例如,從磁盤讀取大文件)或網絡綁定(例如,從FTP服務器下載文件),Python,C和Cython之間的性能差異可能會顯著降低(對于內存綁定操作)或完全消失(對于I / O綁定或網絡綁定操作)
當改進Python的性能是目標符合帕累托原則(帕累托法則,又叫二八法則、80/20原理、帕累托效應。它是指,在任何特定 群體中,重要的因子通常只占少數,而不重要的因子則占多數,因此只要能控制具 有重要性的少數因子即能控制全局。即80%的價值是來自20%的因子,其余的20%的價值則來自80%的因子。)時:我們可以預期程序運行時間中的大約80%僅由代碼的20%引起。那么我們首先要分析一大段python代碼中,占用了大部分運行時間的代碼塊在哪里,然后分析它產生太多時間開銷的原因,如果我們通過分析確定我們程序中的瓶頸是由于它是I / O開銷高或網絡速度的限制,那么我們不可能指望Cython在性能上提供顯著的改進。在轉向Cython之前確定你遇到的性能瓶頸是可以通過cython來解決的,cython它是一個強大的工具,但它必須用正確的方法來使用。因為Cython將C類型系統引入Python,所以C數據類型的所有限制都成為相關問題。在計算大值時,Python整數對象以靜默方式轉換為無限精度的Python長對象。 C int或long是固定精度的,意味著它們無法正確表示無限精度整數。 Cython具有幫助捕獲這些溢出的功能,但更重要的一點仍然是:C數據類型比它們的Python中對應的數據類型要快,但有時不是那么靈活就是了。
讓我們考慮一下Cython的另一個重要功能:與外部代碼連接。假設我們的從是C或C ++代碼的角度來考慮,而不是Python代碼,我們想為它創建Python包裝器。因為Cython了解C和C ++聲明并且可以與外部庫接口,并且因為它可以生成高度優化的代碼,所以我們很容易用它來為c或c++編寫高效的包裝器。
用Cython包裝C代碼
繼續我們的Fibonacci主題,讓我們從C實現開始,并使用Cython將其包裝在Python中。我們函數的接口在cfib.h中:
double cfib(int n){int i;double a=0.0,b=1.0,tmp;for(i=0;i<n;i++){tmp=a;a=a+b;b=tmp;}return a; }cfib.h的Cython包裝器代碼少于10行:
cdef extern from "cfib.h":double cfib(int n)def fib(n):"""Returns the nth Fibonacci number."""return cfib(n)我們在cdef extern語句中提供cfib.h頭文件名,并在塊的縮進體中聲明cfib函數的簽名。在cdef extern塊之后,我們定義了一個fib的 Python包裝函數,它調用cfib并返回其結果。
在將前面的Cython代碼編譯成名為wrap_fib的擴展模塊之后(我們將在第2章中介紹如何編譯Cython代碼的細節),我們可以在python中這么使用它:
我們看到fib函數是wrap_fib擴展模塊中的常規Python函數,并使用Python中的整數90作為參數調用fib函數,為我們調用底層C函數并返回我們期望的結果。總的來說,只使用很少量的Cython代碼就可以包裝一個簡單的函數。而如果要進行手寫C擴展的話,一個手寫的包裝器會需要幾十行C代碼,以及熟悉Python / C API的詳細知識,性能還沒cython好。
這個例子很簡單。如果值在范圍內,則Python int會毫無問題地轉換為C int,否則會引發OverflowError。在內部,Python float類型將其值存儲在C double中,因此cfib返回類型沒有轉換問題。因為我們使用簡單的標量數據,所以Cython可以自動生成類型轉換代碼。在以后的章節中,我們將看到Cython如何幫助我們包裝任意復雜的數據結構,類,函數和方法。因為Cython是一種成熟的語言(而不僅僅是像其他包裝工具那樣的面向對象的特定于域的語言),我們可以使用它在包裝函數調用之前和之后做任何我們喜歡的事情。因為Cython語言理解Python并且可以訪問Python的標準庫,所以我們可以充分利用Python的所有功能和靈活性。
應該注意的是,我們可以在一個文件中使用Cython的兩個raisons d'être (what the fuck??不知道原文寫的這個什么意思)- 加速Python以及調用外部C函數(牛逼,鼓掌)。我們甚至可以在同一個函數內完成!我們將在以后的章節中看到這一點。
補充:Cython的起源Greg Ewing是Cython的前身Pyrex的作者。當Pyrex首次發布時,它通過大量方法加速Python代碼的能力使它立即流行起來。許多項目采用它并開始密集使用它.Pyrex并不打算支持Python語言中的所有構造,但這并沒有限制其最初的成功 - 它滿足了迫切的需求,特別是對于科學計算領域。正如成功的開源項目經常出現的那樣,其他項目組改編并修補Pyrex以滿足他們的需求。在Robert Bradshaw和Stefan Behnel的領導和指導下,Stefan Behnel和William Stein的兩個Pyrex-one叉子最終結合起來形成了Cython項目。
自Cython成立以來,William Stein的Sage項目一直是其發展的主要推動力。 Sage是一個GPL許可的綜合數學軟件系統,旨在為Magma,Maple,Mathematica和Matlab提供可行的替代方案。
Sage廣泛使用Cython來加速以Python為中心的算法,并與數十個C,C ++和Fortran庫進行交互。這是現存最大的Cython項目,擁有數十萬行Cython代碼。如果沒有Sage的支持,Cython很可能不會得到持續的初始支持,成為現在的樣子:一個獨立的,廣泛使用的,積極開發的開源項目。自創建以來,Cython已經擁有了廣闊的目標,首先是完全兼容Python。它還獲得了特定于Python和C之間獨特位置的功能,使Cython更易于使用,更高效,更具表現力。這些Cython的特色功能包括:
C類型和Python類型之間更容易互操作和轉換;
專門的語法,以簡化包裝python和C ++的接口;
特定代碼路徑的自動靜態類型推斷;
具有特定緩沖區語法的一流緩沖區支持(第10章有介紹);
類型化內存視圖(第10章);
基于線程的Prange并行(第12章,這個功能牛逼!)
該項目在其一生中獲得了NSF(通過Sage),華盛頓大學,Enthought(作者的雇主)和幾個Google Summer of Code項目(其中一個項目資助了作者2009年的Cython開發)的資金和支持。 除了明確的資金支持外,Cython還從一個龐大而活躍的開源社區中受益,他們花費了大量時間和精力來開發新功能,實現它們,報告錯誤并修復它們。
總結
本章旨在激發讀者的興趣。 我們已經看到了Cython的基本功能,它們被提煉為最基本的元素。 本書的其余部分深入介紹了Cython語言,介紹了如何編譯和運行Cython代碼,介紹了如何與其進行C和C ++的交互,并提供了許多示例來幫助您在自己的項目中有效地使用Cython。
本篇從整體上介紹了一下cython,啊,迫不及待要用到實戰中去了,回頭再開一個cython實戰的專欄,專門用來放實際上項目中代碼的改進和時間開銷的測試算了。。
總結
以上是生活随笔為你收集整理的python调用c++_python高性能编程之Cython篇 第一章的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 远控时其他用户登录到这台计算机,如何远程
- 下一篇: linux配置redis服务,Linux