File: cfunc.rst

package info (click to toggle)
numba 0.61.2%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 17,316 kB
  • sloc: python: 211,580; ansic: 15,233; cpp: 6,544; javascript: 424; sh: 322; makefile: 173
file content (288 lines) | stat: -rw-r--r-- 8,486 bytes parent folder | download | duplicates (3)
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