[英]Calling Cython functions from Numba jitted code
我知道調用另一個 jitted 函數的 Numba-jitted 函數會識別出這一點並自動使用快速 C 調用約定而不是通過 Python 對象層,因此避免了高 Python 函數調用開銷:
import numba
@numba.jit
def foo(x):
return x**2
@numba.jit
def bar(x):
return 4 * foo(x) # this will be a fast function call
我的問題是,如果我從 Numba 調用 Cython 函數,是否也是如此。 假設我有一個 Cython 模塊foo.pyx
:
cpdef double foo(double x):
return x**2
以及標准的 Python 模塊bar.py
:
import numba
import foo
@numba.jit
def bar(x):
return 4 * foo.foo(x) # will this be a fast function call?
威爾Numba承認foo.foo
自動為C調用的函數或者我需要,比方說手動告訴它,建立一個CFFI包裝?
編輯:經過進一步思考,從 Python 解釋器的角度來看,Cython 函數只是標准的“內置”函數。 所以問題可以變得更普遍:Numba 是否優化了對內置函數和方法的調用以繞過 Python 調用開銷?
可以在 nopython-numba 中使用 Cython 的cpdef
/ cdef
cpdef
(但不是def
-functions):
cdef
/ cpdef
函數必須在 Cython 代碼中標記為api
。numba.extending.get_cython_function_address
可以用來獲取cpdef-function的地址。ctypes
可以用來從cpdef-function的地址創建一個CFunction
,可以在numba-nopython代碼中使用。繼續閱讀以獲得更詳細的解釋。
即使內置函數( PyCFunction
,與 Cython 的def
函數相同)是用 C 編寫的,它們也沒有可供 nopython-numba-code 使用的簽名。
例如來自math
模塊的acos
函數沒有簽名
`double acos(double)`
正如人們所料,但它的簽名是
static PyObject * math_acos(PyObject *self, PyObject *args)
所以基本上為了調用這個函數,numba 需要從手頭的 C-float 構建一個 Python-float,但這被nopython=True
禁止。
然而,Cythons cpdef cpdef
有點不同:它是一個真正的cdef
cpdef
的小包裝,其參數是原始 C 類型,如double
、 int
等。 這個cdef
函數可以被 numba 使用,前提是它的地址是已知的。
用Cython提供了一個辦法,找出的地址cdef
-functions在便攜方式:地址可在屬性中找到__pyx_capi__
的cythonized模塊。
但是,並非所有cdef
和cpdef
函數都以這種方式公開,只有那些明確標記為C-api 聲明或通過pxd
文件隱式共享的pxd
。
一旦foomodule
的函數foo
被標記為api
:
cpdef api double foo(double x):
return x*x
cpdef 函數foo
的地址可以在foomodule.__pyx_capi__
-dictionary 中找到:
import foomodule
foomodule.__pyx_capi
# {'foo': <capsule object "double (double)" at 0x7fe0a46f0360>}
在 Python 中從PyCapsule
中提取地址非常困難。 一種可能性是使用ctypes.pythonapi
,另一種(可能更簡單)是利用 Cython 來訪問 Python 的 C-API:
%%cython
from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_GetName
def address_from_capsule(object capsule):
name = PyCapsule_GetName(capsule)
return <unsigned long long int> PyCapsule_GetPointer(capsule, name)
可以用作:
addr = address_from_capsule(foomodule.__pyx_capi__['foo'])
但是,numba 提供了一個開箱即用的類似功能 - get_cython_function_address
:
from numba.extending import get_cython_function_address
addr = get_cython_function_address("foomodule", "foo")
一旦我們得到了 c 函數的地址,我們就可以構造一個ctypes
函數:
import ctypes
foo_functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)
foo_for_numba = foo_functype(addr)
例如,可以從 nopython-numba 使用此函數,如下所示:
from numba import njit
@njit
def use_foo(x):
return foo_for_numba(x)
現在:
use_foo(5)
# 25.0
產生預期的結果。
numba 知道如何將一組有限的內置函數(來自 python 標准庫和 numpy)轉換為本機代碼:
在nopython
模式下,Numba 將無法執行其他任何操作,因此求助於更慢的objectmode
。
沒有直接的方法可以將 cython 函數傳遞給 Numba 並使其在nopython
模式下被識別。 Numba 確實有 cffi 的鈎子:
http://numba.pydata.org/numba-doc/latest/reference/pysupported.html#cffi
可以利用它來調用外部 C 代碼,如果您可以在 C 級別創建一個低級包裝器,您可能能夠調用 cython; 不過,我不是 100% 確定這是否可能。 我寫過這樣做是為了從 Numba 調用 RMath 函數:
如果你走那條路,它可能會幫助你開始。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.