1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
|
.. _cfunc:
====================================
Creating C callbacks with ``@cfunc``
====================================
Interfacing with some native libraries (for example written in C or C++)
can necessitate writing native callbacks to provide business logic to the
library. The :func:`numba.cfunc` decorator creates a compiled function
callable from foreign C code, using the signature of your choice.
Basic usage
===========
The ``@cfunc`` decorator has a similar usage to ``@jit``, but with an
important difference: passing a single signature is mandatory.
It determines the visible signature of the C callback::
from numba import cfunc
@cfunc("float64(float64, float64)")
def add(x, y):
return x + y
The C function object exposes the address of the compiled C callback as
the :attr:`~CFunc.address` attribute, so that you can pass it to any
foreign C or C++ library. It also exposes a :mod:`ctypes` callback
object pointing to that callback; that object is also callable from
Python, making it easy to check the compiled code::
@cfunc("float64(float64, float64)")
def add(x, y):
return x + y
print(add.ctypes(4.0, 5.0)) # prints "9.0"
Example
=======
In this example, we are going to be using the ``scipy.integrate.quad``
function. That function accepts either a regular Python callback or
a C callback wrapped in a :mod:`ctypes` callback object.
Let's define a pure Python integrand and compile it as a
C callback::
>>> import numpy as np
>>> from numba import cfunc
>>> def integrand(t):
return np.exp(-t) / t**2
...:
>>> nb_integrand = cfunc("float64(float64)")(integrand)
We can pass the ``nb_integrand`` object's :mod:`ctypes` callback to
``scipy.integrate.quad`` and check that the results are the same as with
the pure Python function::
>>> import scipy.integrate as si
>>> def do_integrate(func):
"""
Integrate the given function from 1.0 to +inf.
"""
return si.quad(func, 1, np.inf)
...:
>>> do_integrate(integrand)
(0.14849550677592208, 3.8736750296130505e-10)
>>> do_integrate(nb_integrand.ctypes)
(0.14849550677592208, 3.8736750296130505e-10)
Using the compiled callback, the integration function does not invoke the
Python interpreter each time it evaluates the integrand. In our case, the
integration is made 18 times faster::
>>> %timeit do_integrate(integrand)
1000 loops, best of 3: 242 µs per loop
>>> %timeit do_integrate(nb_integrand.ctypes)
100000 loops, best of 3: 13.5 µs per loop
Dealing with pointers and array memory
======================================
A less trivial use case of C callbacks involves doing operation on some
array of data passed by the caller. As C doesn't have a high-level
abstraction similar to Numpy arrays, the C callback's signature will pass
low-level pointer and size arguments. Nevertheless, the Python code for
the callback will expect to exploit the power and expressiveness of Numpy
arrays.
In the following example, the C callback is expected to operate on 2-d arrays,
with the signature ``void(double *input, double *output, int m, int n)``.
You can implement such a callback thusly::
from numba import cfunc, types, carray
c_sig = types.void(types.CPointer(types.double),
types.CPointer(types.double),
types.intc, types.intc)
@cfunc(c_sig)
def my_callback(in_, out, m, n):
in_array = carray(in_, (m, n))
out_array = carray(out, (m, n))
for i in range(m):
for j in range(n):
out_array[i, j] = 2 * in_array[i, j]
The :func:`numba.carray` function takes as input a data pointer and a shape
and returns an array view of the given shape over that data. The data is
assumed to be laid out in C order. If the data is laid out in Fortran order,
:func:`numba.farray` should be used instead.
Handling C structures
=====================
With CFFI
---------
For applications that have a lot of state, it is useful to pass data in C
structures. To simplify the interoperability with C code, numba can convert
a ``cffi`` type into a numba ``Record`` type using
``numba.core.typing.cffi_utils.map_type``::
from numba.core.typing import cffi_utils
nbtype = cffi_utils.map_type(cffi_type, use_record_dtype=True)
.. note:: **use_record_dtype=True** is needed otherwise pointers to C
structures are returned as void pointers.
.. note:: From v0.49 the ``numba.cffi_support`` module has been phased out
in favour of ``numba.core.typing.cffi_utils``
For example::
from cffi import FFI
src = """
/* Define the C struct */
typedef struct my_struct {
int i1;
float f2;
double d3;
float af4[7]; // arrays are supported
} my_struct;
/* Define a callback function */
typedef double (*my_func)(my_struct*, size_t);
"""
ffi = FFI()
ffi.cdef(src)
# Get the function signature from *my_func*
sig = cffi_utils.map_type(ffi.typeof('my_func'), use_record_dtype=True)
# Make the cfunc
from numba import cfunc, carray
@cfunc(sig)
def foo(ptr, n):
base = carray(ptr, n) # view pointer as an array of my_struct
tmp = 0
for i in range(n):
tmp += base[i].i1 * base[i].f2 / base[i].d3
tmp += base[i].af4.sum() # nested arrays are like normal NumPy arrays
return tmp
With ``numba.types.Record.make_c_struct``
-----------------------------------------
The ``numba.types.Record`` type can be created manually to follow a
C-structure's layout. To do that, use ``Record.make_c_struct``, for example::
my_struct = types.Record.make_c_struct([
# Provides a sequence of 2-tuples i.e. (name:str, type:Type)
('i1', types.int32),
('f2', types.float32),
('d3', types.float64),
('af4', types.NestedArray(dtype=types.float32, shape=(7,))),
])
Due to ABI limitations, structures should be passed as pointers
using ``types.CPointer(my_struct)`` as the argument type. Inside the ``cfunc``
body, the ``my_struct*`` can be accessed with ``carray``.
Full example
------------
See full example in ``examples/notebooks/Accessing C Struct Data.ipynb``.
Signature specification
=======================
The explicit ``@cfunc`` signature can use any :ref:`Numba types <numba-types>`,
but only a subset of them make sense for a C callback. You should
generally limit yourself to scalar types (such as ``int8`` or ``float64``)
,pointers to them (for example ``types.CPointer(types.int8)``), or pointers
to ``Record`` type.
Compilation options
===================
A number of keyword-only arguments can be passed to the ``@cfunc``
decorator: ``nopython`` and ``cache``. Their meaning is similar to those
in the ``@jit`` decorator.
Calling C code from Numba
=========================
It is also possible to call C code from Numba ``@jit`` functions. In this
example, we are going to be compiling a simple function ``sum`` that adds two
integers and calling it within Numba ``@jit`` code.
.. note::
The example below was tested on Linux and will likely work on Unix-like
operating systems.
.. code-block:: C
#include <stdint.h>
int64_t sum(int64_t a, int64_t b){
return a + b;
}
Compile the code with ``gcc lib.c -fPIC -shared -o shared_library.so`` to
generate a shared library.
.. code-block:: python
from numba import njit
from numba.core import types, typing
from llvmlite import binding
import os
# load the library into LLVM
path = os.path.abspath('./shared_library.so')
binding.load_library_permanently(path)
# Adds typing information
c_func_name = 'sum'
return_type = types.int64
argty = types.int64
c_sig = typing.signature(return_type, argty, argty)
c_func = types.ExternalFunction(c_func_name, c_sig)
@njit
def example(x, y):
return c_func(x, y)
print(example(3, 4)) # 7
It is also possible to use ``ctypes`` as well to call C functions. The advantage
of using ``ctypes`` is that it is invariant to the usage of JIT decorators.
.. code-block:: python
from numba import njit
import ctypes
DSO = ctypes.CDLL('./shared_library.so')
# Add typing information
c_func = DSO.sum
c_func.restype = ctypes.c_int
c_func.argtypes = [ctypes.c_int, ctypes.c_int]
@njit
def example(x, y):
return c_func(x, y)
print(example(3, 4)) # 7
print(example.py_func(3, 4)) # 7
|