1.8。使用@cfunc 创建 C 回调

原文: http://numba.pydata.org/numba-doc/latest/user/cfunc.html

与某些本机库(例如,用 C 或 C ++编写)连接可能需要编写本机回调以向库提供业务逻辑。 numba.cfunc() 装饰器使用您选择的签名创建可从外部 C 代码调用的编译函数。

1.8.1。基本用法

@cfunc装饰器与@jit具有相似的用法,但有一个重要区别:传递单个签名是强制性的。它确定 C 回调的可见签名:

  1. from numba import cfunc
  2. @cfunc("float64(float64, float64)")
  3. def add(x, y):
  4. return x + y

C 函数对象将已编译的 C 回调的地址公开为 address 属性,以便您可以将其传递给任何外部 C 或 C ++库。它还暴露了指向该回调的 ctypes 回调对象;该对象也可以从 Python 调用,从而可以轻松检查已编译的代码:

  1. @cfunc("float64(float64, float64)")
  2. def add(x, y):
  3. return x + y
  4. print(add.ctypes(4.0, 5.0)) # prints "9.0"

1.8.2。示例

在这个例子中,我们将使用scipy.integrate.quad函数。该函数接受常规 Python 回调或包含在 ctypes 回调对象中的 C 回调。

让我们定义一个纯 Python 的 integrand 并将其编译为 C 回调:

  1. >>> import numpy as np
  2. >>> from numba import cfunc
  3. >>> def integrand(t):
  4. return np.exp(-t) / t**2
  5. ...:
  6. >>> nb_integrand = cfunc("float64(float64)")(integrand)

我们可以将nb_integrand对象的 ctypes 回调传递给scipy.integrate.quad,并检查结果是否与纯 Python 函数相同:

  1. >>> import scipy.integrate as si
  2. >>> def do_integrate(func):
  3. """
  4. Integrate the given function from 1.0 to +inf.
  5. """
  6. return si.quad(func, 1, np.inf)
  7. ...:
  8. >>> do_integrate(integrand)
  9. (0.14849550677592208, 3.8736750296130505e-10)
  10. >>> do_integrate(nb_integrand.ctypes)
  11. (0.14849550677592208, 3.8736750296130505e-10)

使用已编译的回调,集成函数在每次评估被积函数时都不会调用 Python 解释器。在我们的例子中,集成速度提高了 18 倍:

  1. >>> %timeit do_integrate(integrand)
  2. 1000 loops, best of 3: 242 µs per loop
  3. >>> %timeit do_integrate(nb_integrand.ctypes)
  4. 100000 loops, best of 3: 13.5 µs per loop

1.8.3。处理指针和数组内存

C 回调的一个不太重要的用例涉及对调用者传递的某些数据数组进行操作。由于 C 没有类似于 Numpy 数组的高级抽象,C 回调的签名将传递低级指针和大小参数。然而,回调的 Python 代码将期望利用 Numpy 数组的强大功能和表现力。

在下面的示例中,C 回调预计将在 2-d 数组上运行,签名为void(double *input, double *output, int m, int n)。你可以这样实现这样的回调:

  1. from numba import cfunc, types, carray
  2. c_sig = types.void(types.CPointer(types.double),
  3. types.CPointer(types.double),
  4. types.intc, types.intc)
  5. @cfunc(c_sig)
  6. def my_callback(in_, out, m, n):
  7. in_array = carray(in_, (m, n))
  8. out_array = carray(out, (m, n))
  9. for i in range(m):
  10. for j in range(n):
  11. out_array[i, j] = 2 * in_array[i, j]

numba.carray() 函数将数据指针和形状作为输入,并返回给定形状的数组视图。假设数据按 C 顺序排列。如果数据以 Fortran 顺序排列,则应使用 numba.farray()

1.8.4。处理 C 结构

1.8.4.1。用 CFFI

对于具有大量状态的应用程序,在 C 结构中传递数据很有用。为了简化与 C 代码的互操作性,numba 可以使用numba.cffi_support.map_typecffi类型转换为 numba Record类型:

  1. from numba import cffi_support
  2. nbtype = cffi_support.map_type(cffi_type, use_record_dtype=True)

注意

use_record_dtype = True 是必需的,否则指向 C 结构的指针将作为 void 指针返回。

例如:

  1. from cffi import FFI
  2. src = """
  3. /* Define the C struct */
  4. typedef struct my_struct {
  5. int i1;
  6. float f2;
  7. double d3;
  8. float af4[7]; // arrays are supported
  9. } my_struct;
  10. /* Define a callback function */
  11. typedef double (*my_func)(my_struct*, size_t);
  12. """
  13. ffi = FFI()
  14. ffi.cdef(src)
  15. # Get the function signature from *my_func*
  16. sig = cffi_support.map_type(ffi.typeof('my_func'), use_record_dtype=True)
  17. # Make the cfunc
  18. from numba import cfunc, carray
  19. @cfunc(sig)
  20. def foo(ptr, n):
  21. base = carray(ptr, n) # view pointer as an array of my_struct
  22. tmp = 0
  23. for i in range(n):
  24. tmp += base[i].i1 * base[i].f2 / base[i].d3
  25. tmp += base[i].af4.sum() # nested arrays are like normal numpy array
  26. return tmp

1.8.4.2。用numba.types.Record.make_c_struct

可以手动创建numba.types.Record类型以遵循 C 结构的布局。为此,请使用Record.make_c_struct,例如:

  1. my_struct = types.Record.make_c_struct([
  2. # Provides a sequence of 2-tuples i.e. (name:str, type:Type)
  3. ('i1', types.int32),
  4. ('f2', types.float32),
  5. ('d3', types.float64),
  6. ('af4', types.NestedArray(dtype=types.float32, shape=(7,))),
  7. ])

由于 ABI 限制,应使用types.CPointer(my_struct)作为参数类型将结构作为指针传递。在cfunc体内,可以使用carray访问my_struct*

1.8.4.3。完整示例

请参阅examples/notebooks/Accessing C Struct Data.ipynb中的完整示例。

1.8.5。签名规范

显式@cfunc签名可以使用任何 Numba 类型,但只有它们的一个子集对 C 回调有意义。您通常应将自己限制为标量类型(例如int8float64),指向它们的指针(例如types.CPointer(types.int8))或指向Record类型的指针。

1.8.6。编译选项

可以将许多仅关键字参数传递给@cfunc装饰器:nopythoncache。它们的含义类似于@jit装饰器中的含义。