从 Numba jitted 代码调用 Cython 函数
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 函数只是标准的 "builtin" 函数。所以问题可以更笼统:Numba 是否优化了对内置函数和方法的调用以绕过 Python 调用开销?
numba 知道如何将一组有限的内置函数(来自 python 标准库和 numpy)转换为本机代码:
- http://numba.pydata.org/numba-doc/latest/reference/pysupported.html
- http://numba.pydata.org/numba-doc/latest/reference/numpysupported.html
在 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 函数:
如果你走那条路,它可能有助于你入门。
可以在 nopython-numba:
中使用 Cython 的 cpdef
/cdef
-函数(但不能使用 def
-函数)
- 步骤:
cdef
/cpdef
函数在 Cython 代码中必须是 marked as api
。
- 步骤:
numba.extending.get_cython_function_address
可用于获取cpdef-function的地址。
- 步骤:
ctypes
可用于从cpdef-function的地址创建CFunction
,可用于numba-nopython代码。
继续阅读以获得更详细的解释。
即使 builtin-functions(PyCFunction
,与 Cython 的 def
函数相同)是用 C 语言编写的,它们也没有可供 nopython-numba-代码.
例如 math
模块中的 acos
函数没有签名
`double acos(double)`
如人们所料,但 its signature 是
static PyObject * math_acos(PyObject *self, PyObject *args)
所以基本上为了调用这个函数,numba 需要从手头的 C-float 构建一个 Python-float,但这被 nopython=True
.[=60= 禁止]
然而,Cythons cpdef
-函数有点不同:它是一个围绕真实 cdef
-函数的小包装器,其参数是原始的 C-types,如 [=36] =]、int
等。这个 cdef
函数可以被 numba 使用,前提是它的地址是已知的。
Cython 提供了一种以可移植的方式查找 cdef
函数地址的方法:地址可以在 cython 化模块的属性 __pyx_capi__
中找到。
然而,并非所有 cdef
和 cpdef
函数都以这种方式公开,只有明确标记为 C-api declarations 或通过 [=43 共享的隐式=]-文件.
一旦foomodule
的函数foo
被标记为api
:
cpdef api double foo(double x):
return x*x
cpdef-functionfoo
的地址可以在foomodule.__pyx_capi__
字典中找到:
import foomodule
foomodule.__pyx_capi
# {'foo': <capsule object "double (double)" at 0x7fe0a46f0360>}
从 PyCapsule
in Python. One possibility is to use 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-function的地址,我们就可以构造一个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
产生了预期的结果。
我知道调用另一个 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 函数只是标准的 "builtin" 函数。所以问题可以更笼统:Numba 是否优化了对内置函数和方法的调用以绕过 Python 调用开销?
numba 知道如何将一组有限的内置函数(来自 python 标准库和 numpy)转换为本机代码:
- http://numba.pydata.org/numba-doc/latest/reference/pysupported.html
- http://numba.pydata.org/numba-doc/latest/reference/numpysupported.html
在 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 函数:
如果你走那条路,它可能有助于你入门。
可以在 nopython-numba:
中使用 Cython 的cpdef
/cdef
-函数(但不能使用 def
-函数)
- 步骤:
cdef
/cpdef
函数在 Cython 代码中必须是 marked asapi
。 - 步骤:
numba.extending.get_cython_function_address
可用于获取cpdef-function的地址。 - 步骤:
ctypes
可用于从cpdef-function的地址创建CFunction
,可用于numba-nopython代码。
继续阅读以获得更详细的解释。
即使 builtin-functions(PyCFunction
,与 Cython 的 def
函数相同)是用 C 语言编写的,它们也没有可供 nopython-numba-代码.
例如 math
模块中的 acos
函数没有签名
`double acos(double)`
如人们所料,但 its signature 是
static PyObject * math_acos(PyObject *self, PyObject *args)
所以基本上为了调用这个函数,numba 需要从手头的 C-float 构建一个 Python-float,但这被 nopython=True
.[=60= 禁止]
然而,Cythons cpdef
-函数有点不同:它是一个围绕真实 cdef
-函数的小包装器,其参数是原始的 C-types,如 [=36] =]、int
等。这个 cdef
函数可以被 numba 使用,前提是它的地址是已知的。
Cython 提供了一种以可移植的方式查找 cdef
函数地址的方法:地址可以在 cython 化模块的属性 __pyx_capi__
中找到。
然而,并非所有 cdef
和 cpdef
函数都以这种方式公开,只有明确标记为 C-api declarations 或通过 [=43 共享的隐式=]-文件.
一旦foomodule
的函数foo
被标记为api
:
cpdef api double foo(double x):
return x*x
cpdef-functionfoo
的地址可以在foomodule.__pyx_capi__
字典中找到:
import foomodule
foomodule.__pyx_capi
# {'foo': <capsule object "double (double)" at 0x7fe0a46f0360>}
从 PyCapsule
in Python. One possibility is to use 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-function的地址,我们就可以构造一个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
产生了预期的结果。