Calling Cython functions from Numba jitted code
There is a limited set of builtin functions (from both the python standard library and numpy) that numba knows how to translate into native code:
- http://numba.pydata.org/numba-doc/latest/reference/pysupported.html
- http://numba.pydata.org/numba-doc/latest/reference/numpysupported.html
Anything else will not be able to be jitted by Numba in nopython
mode, thus resorting to objectmode
which is much slower.
There is no direct way to pass a cython function to Numba and have it be recognized in nopython
mode. Numba does have hooks for cffi:
http://numba.pydata.org/numba-doc/latest/reference/pysupported.html#cffi
that can be leveraged to call outside C code, that you might be able to rig up to call cython if you could create a low level wrapper at the C-level; I'm not 100% sure if this is possible though. I wrote about doing this for calling RMath functions from Numba:
https://web.archive.org/web/20160611082327/https://www.continuum.io/blog/developer-blog/calling-c-libraries-numba-using-cffi
It might be helpful in getting you started if you go that route.
It is possible to use Cython's cpdef
/cdef
-functions (but not the def
-functions) in nopython-numba:
- step:
cdef
/cpdef
function must be marked asapi
in the Cython code. - step:
numba.extending.get_cython_function_address
can be used to get the address of the cpdef-function. - step:
ctypes
can be used to create aCFunction
from the address of the cpdef-function, which can be used in numba-nopython code.
Read on for a more detailed explanaton.
Even if the builtin-functions (PyCFunction
, the same as Cython's def
-functions) are written in C, they don't have a signature which could be used by nopython-numba-code.
For example the acos
function from the math
-module, doesn't have the signature
`double acos(double)`
as one could expect, but its signature is
static PyObject * math_acos(PyObject *self, PyObject *args)
So basically in order to call this function, numba would need to build a Python-float from the C-float at hand, but this is prohibited by nopython=True
.
However, Cythons cpdef
-functions are a little bit different: it is a small wrapper around a real cdef
-function, whose arguments are raw C-types like double
, int
and so on. This cdef
-function could be used by numba, only if its address were known.
Cython offers a way to find out the addresses of cdef
-functions in a portable way: the addresses can be found in the attribute __pyx_capi__
of the cythonized module.
However, not all cdef
and cpdef
functions are exposed in this way, but only ones which are explicitly marked as C-api declarations or implicitly by being shared through a pxd
-file.
Once the function foo
of the foomodule
is marked as api
:
cpdef api double foo(double x):
return x*x
the address of the cpdef-function foo
can be found in foomodule.__pyx_capi__
-dictionary:
import foomodule
foomodule.__pyx_capi
# {'foo': <capsule object "double (double)" at 0x7fe0a46f0360>}
It is surprisingly hard to extract the address from a PyCapsule
in Python. One possibility is to use ctypes.pythonapi
, another (maybe easier one) is to utilize Cython to access Python's 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)
which can be used as:
addr = address_from_capsule(foomodule.__pyx_capi__['foo'])
However, numba offers a similar functionality out of the box - get_cython_function_address
:
from numba.extending import get_cython_function_address
addr = get_cython_function_address("foomodule", "foo")
Once we got the address of the c-function, we can construct a ctypes
-function:
import ctypes
foo_functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)
foo_for_numba = foo_functype(addr)
This function can be utilized for example as follows from nopython-numba:
from numba import njit
@njit
def use_foo(x):
return foo_for_numba(x)
and now:
use_foo(5)
# 25.0
yields the expected result.