企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 1.8。使用`@cfunc` 创建 C 回调 > 原文: [http://numba.pydata.org/numba-doc/latest/user/cfunc.html](http://numba.pydata.org/numba-doc/latest/user/cfunc.html) 与某些本机库(例如,用 C 或 C ++编写)连接可能需要编写本机回调以向库提供业务逻辑。 [`numba.cfunc()`](../reference/jit-compilation.html#numba.cfunc "numba.cfunc") 装饰器使用您选择的签名创建可从外部 C 代码调用的编译函数。 ## 1.8.1。基本用法 `@cfunc`装饰器与`@jit`具有相似的用法,但有一个重要区别:传递单个签名是强制性的。它确定 C 回调的可见签名: ```py from numba import cfunc @cfunc("float64(float64, float64)") def add(x, y): return x + y ``` C 函数对象将已编译的 C 回调的地址公开为 [`address`](../reference/jit-compilation.html#CFunc.address "CFunc.address") 属性,以便您可以将其传递给任何外部 C 或 C ++库。它还暴露了指向该回调的 [`ctypes`](https://docs.python.org/3/library/ctypes.html#module-ctypes "(in Python v3.7)") 回调对象;该对象也可以从 Python 调用,从而可以轻松检查已编译的代码: ```py @cfunc("float64(float64, float64)") def add(x, y): return x + y print(add.ctypes(4.0, 5.0)) # prints "9.0" ``` ## 1.8.2。示例 在这个例子中,我们将使用`scipy.integrate.quad`函数。该函数接受常规 Python 回调或包含在 [`ctypes`](https://docs.python.org/3/library/ctypes.html#module-ctypes "(in Python v3.7)") 回调对象中的 C 回调。 让我们定义一个纯 Python 的 integrand 并将其编译为 C 回调: ```py >>> import numpy as np >>> from numba import cfunc >>> def integrand(t): return np.exp(-t) / t**2 ...: >>> nb_integrand = cfunc("float64(float64)")(integrand) ``` 我们可以将`nb_integrand`对象的 [`ctypes`](https://docs.python.org/3/library/ctypes.html#module-ctypes "(in Python v3.7)") 回调传递给`scipy.integrate.quad`,并检查结果是否与纯 Python 函数相同: ```py >>> 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) ``` 使用已编译的回调,集成函数在每次评估被积函数时都不会调用 Python 解释器。在我们的例子中,集成速度提高了 18 倍: ```py >>> %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 ``` ## 1.8.3。处理指针和数组内存 C 回调的一个不太重要的用例涉及对调用者传递的某些数据数组进行操作。由于 C 没有类似于 Numpy 数组的高级抽象,C 回调的签名将传递低级指针和大小参数。然而,回调的 Python 代码将期望利用 Numpy 数组的强大功能和表现力。 在下面的示例中,C 回调预计将在 2-d 数组上运行,签名为`void(double *input, double *output, int m, int n)`。你可以这样实现这样的回调: ```py 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] ``` [`numba.carray()`](../reference/utils.html#numba.carray "numba.carray") 函数将数据指针和形状作为输入,并返回给定形状的数组视图。假设数据按 C 顺序排列。如果数据以 Fortran 顺序排列,则应使用 [`numba.farray()`](../reference/utils.html#numba.farray "numba.farray") 。 ## 1.8.4。处理 C 结构 ### 1.8.4.1。用 CFFI 对于具有大量状态的应用程序,在 C 结构中传递数据很有用。为了简化与 C 代码的互操作性,numba 可以使用`numba.cffi_support.map_type`将`cffi`类型转换为 numba `Record`类型: ```py from numba import cffi_support nbtype = cffi_support.map_type(cffi_type, use_record_dtype=True) ``` 注意 **use_record_dtype = True** 是必需的,否则指向 C 结构的指针将作为 void 指针返回。 例如: ```py 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_support.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 array return tmp ``` ### 1.8.4.2。用`numba.types.Record.make_c_struct` 可以手动创建`numba.types.Record`类型以遵循 C 结构的布局。为此,请使用`Record.make_c_struct`,例如: ```py 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,))), ]) ``` 由于 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 类型](../reference/types.html#numba-types),但只有它们的一个子集对 C 回调有意义。您通常应将自己限制为标量类型(例如`int8`或`float64`),指向它们的指针(例如`types.CPointer(types.int8)`)或指向`Record`类型的指针。 ## 1.8.6。编译选项 可以将许多仅关键字参数传递给`@cfunc`装饰器:`nopython`和`cache`。它们的含义类似于`@jit`装饰器中的含义。