Cython 最佳實務、慣例與知識#
本文檔提供在 scikit-learn 中開發 Cython 程式碼的技巧。
在 scikit-learn 中使用 Cython 開發的技巧#
簡化開發的技巧#
花時間閱讀 Cython 的文件並非浪費時間。
如果您打算使用 OpenMP:在 MacOS 上,系統發行的
clang
沒有實作 OpenMP。您可以安裝conda-forge
上提供的compilers
套件,其中包含 OpenMP 的實作。啟用檢查可能有幫助。例如,要啟用邊界檢查,請使用
export SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES=1
從筆記本從頭開始,以了解如何使用 Cython 並快速獲得對您工作的回饋。如果您計劃在 Jupyter 筆記本中使用 OpenMP 進行實作,請在 Cython magic 中添加額外的編譯器和連結器引數。
# For GCC and for clang %%cython --compile-args=-fopenmp --link-args=-fopenmp # For Microsoft's compilers %%cython --compile-args=/openmp --link-args=/openmp
若要除錯 C 程式碼(例如,segfault),請使用
gdb
搭配gdb --ex r --args python ./entrypoint_to_bug_reproducer.py
若要在
cdef (nogil)
環境中存取某些值以進行除錯,請使用with gil: print(state_to_print)
請注意,Cython 無法解析帶有
{var=}
運算式的 f 字串,例如print(f"{test_val=}")
scikit-learn 程式碼庫中有很多未統一(融合)的類型(重新)定義。目前有正在進行的工作,以簡化和統一整個程式碼庫。目前,請確保您了解最終使用了哪些具體類型。
您可能會發現此別名對於編譯個別的 Cython 擴充功能很有用
# You might want to add this alias to your shell script config. alias cythonX="cython -X language_level=3 -X boundscheck=False -X wraparound=False -X initializedcheck=False -X nonecheck=False -X cdivision=True" # This generates `source.c` as if you had recompiled scikit-learn entirely. cythonX --annotate source.pyx
使用帶有此旗標的
--annotate
選項可以產生程式碼註解的 HTML 報告。此報告逐行指示與 CPython 直譯器的互動。在演算法的計算密集區段中,必須盡可能避免與 CPython 直譯器的互動。如需更多資訊,請參閱 Cython 教學課程的此章節。# This generates a HTML report (`source.html`) for `source.c`. cythonX --annotate source.pyx
效能技巧#
了解 CPython 環境中的 GIL(它解決了哪些問題,它的限制是什麼),並充分了解 Cython 何時會對應到與 CPython 無互動的 C 程式碼,何時不會,以及何時不能(例如,存在與 Python 物件的互動,包括函式)。在這方面,PEP073 提供了良好的概述、環境和移除路徑。
請確保您已停用檢查。
盡可能始終優先使用 memoryview 而不是
cnp.ndarray
:memoryview 很輕量。避免 memoryview 切片:memoryview 切片在某些情況下可能很昂貴或具有誤導性,我們最好不要使用它,即使在某些情況下處理較少的維度會更好。
使用
@final
裝飾最終類別或方法(這允許在需要時移除虛擬表)在有意義時內嵌方法和函式
如有疑問,請閱讀產生的 C 或 C++ 程式碼:對於一行 Cython 程式碼來說,「C 指令和間接越少越好」是一個很好的經驗法則。
nogil
宣告只是提示:當將cdef
函式宣告為 nogil 時,表示它們可以在不持有 GIL 的情況下被呼叫,但它不會在進入它們時釋放 GIL。您必須自行執行此操作,方法是將nogil=True
明確傳遞給cython.parallel.prange
,或使用明確的內容管理員cdef inline void my_func(self) nogil: # Some logic interacting with CPython, e.g. allocating arrays via NumPy. with nogil: # The code here is run as is it were written in C. return 0
此項目基於 Stéfan’s Benhel 的這則評論
可以透過
sklearn.utils._cython_blas
中定義的介面直接呼叫 BLAS 常式。
使用 OpenMP#
由於 scikit-learn 可以在不使用 OpenMP 的情況下建置,因此必須保護每個對 OpenMP 的直接呼叫。
_openmp_helpers
模組,可在 sklearn/utils/_openmp_helpers.pyx 中取得,提供受保護版本的 OpenMP 常式。若要使用 OpenMP 常式,它們必須從此模組 cimported
,而不是直接從 OpenMP 程式庫中 cimported
from sklearn.utils._openmp_helpers cimport omp_get_max_threads
max_threads = omp_get_max_threads()
平行迴圈 prange
已經受到 cython 保護,可以直接從 cython.parallel
使用。
類型#
Cython 程式碼需要使用明確的類型。這是您獲得效能提升的原因之一。為了避免程式碼重複,我們在 sklearn/utils/_typedefs.pyd 中提供了最常用類型的中心位置。理想情況下,您可以先查看此處,然後 cimport
您需要的類型,例如
from sklear.utils._typedefs cimport float32, float64