注:本文中的代碼是在GNU AGPLv3下授權的。
當我沒有找到一個整體式的使用ctypes的指南時,我寫了這個指南。希望這能讓別人的生活更輕鬆一些。
基本優化在用C語言重寫Python原始碼之前,請考慮一下這些標準的Python優化。
內置數據結構Python中內置的數據結構(如set和dict)是用C編寫的。它們比將你自己的數據結構編寫為Python類要快得多。除了標準的set、dict、list和tuple之外,其他數據結構都記錄在collections模塊中。
列表推導式與其向一個列表附加內容,不如使用列表推導式。
ctypesctypes是一個模塊,它允許你通過Python代碼與C代碼進行通信,而不需要使用子進程或類似的模塊來從CLI運行另一個進程。
這有兩個部分:編譯要作為共享對象加載的C代碼,並在Python代碼中設置數據結構,從而映射到C-types。
在這篇文章中,我將把我的Python代碼連接到lcs.c,它可以找到兩個字符串列表的最長公共子序列。在Python代碼中,我想實現這樣的效果:
這個特殊的C函數的一些挑戰是函數籤名的參數類型是字符串列表,而返回類型的長度不是固定的。我用一個包含指針和長度的Sequence結構來解決這個問題。
為Python編譯C代碼首先,C原始碼(lcs.c)被編譯為可以被Python裝載的lcs.so。
接下來,我們開始編寫Python代碼來使用這個共享對象文件。
Python中的結構體下面,我將展示我的C原始碼中使用的兩個結構體。
在這裡,你可以看到結構體的Python翻譯。
一些註解:
所有結構體都是從ctypes.Structure繼承的class。
惟一的欄位是_fields_,它是一個元組列表。每個元組是(<variable-name>, <ctypes.TYPE>)。
ctypes還有像c_char (char)和c_char_p (*char)這樣的類型。
ctypes還包括POINTER(),它會從傳遞給它的任何類型創建一個指針類型。
如果你有一個類似於CELL中的遞歸定義,則你必須傳遞初始聲明,然後添加_fields_欄位以供以後引用。
由於我在我的Python代碼中沒有使用CELL,所以我不需要將這個結構體寫出來,但是它在遞歸欄位中有一個有趣的特性。
調用你的C代碼另外,你需要一些代碼來將Python類型轉換為新的C結構。最後,你可以使用新的C函數來加速Python代碼。
更多註解:
**char(字符串列表)在Python中直接映射到字節列表。
lcs.c有一個函數lcs(),它的籤名是:struct Sequence *lcs(struct Sequence *s1, struct Sequence *s2)。要設置其返回類型,我使用lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)。
為了調用一個對struct Sequence的引用,我使用ctypes.byref(),它返回一個指向你的對象的「輕量級指針」(比ctypes.POINTER()更快)。
common.items是一個字節列表,因此它們被解碼後得到的ret將是一個str列表。
lcsmodule.freeSequence(common)只是釋放與common相關的內存。這很重要,因為它不會被垃圾回收器(AFAIK)收集。
優化的Python代碼: 你用C編寫的代碼,並用Python為它編寫包裝器。
額外擴展: PyPy註:我個人從未使用過PyPy。
一個簡單的優化就是在PyPy運行時中運行你的程序,其中包括一個即時(JIT)編譯器,當代碼循環多次運行時,它會通過將它們編譯成本機代碼來加速循環。
如果你有任何意見或想進一步討論,請發郵件給我。
[相關連結]:https://www.youtube.com/watch?v=SHbS9tYFpcQ
Sam Stevens, 2019年
[原始碼]:https://github.com/samuelstevens/personal-website
英文原文:https://samuelstevens.me/writing/optimizing-python-code-with-ctypes