NBEP 4:定义 C 回调
- 作者
Antoine Pitrou
- 日期
2016 年 4 月
- 状态
草稿
与某些原生库(例如用 C 或 C++ 编写的库)交互时,可能需要编写原生回调函数以向库提供业务逻辑。一些面向 Python 的库也可能提供一种替代方案,即传递一个 ctypes 封装的原生回调函数,而不是 Python 回调函数,以获得更好的性能。一个简单的例子是 scipy.integrate
包,用户将要积分的函数作为回调函数传递。
这些库的用户可能希望在用 Python 编写代码的同时,受益于运行纯原生代码的性能优势。本提案概述了一种在 Numba 中提供此类功能的方案。
基本用法
我们建议添加一个新的装饰器 @cfunc
,可以从主包中导入。这个装饰器允许定义回调函数,如下面的示例所示:
from numba import cfunc
from numba.types import float64
# A callback with the C signature `double(double)`
@cfunc(float64(float64), nopython=True)
def integrand(x):
return 1 / x
@cfunc
装饰器返回一个“C 函数”对象,该对象包含运行给定编译函数所需的资源(例如其 LLVM 模块)。此对象具有多个属性和方法:
ctypes
属性是一个 ctypes 函数对象,表示原生函数。address
属性是原生函数代码的地址,以整数形式表示(注意,这也可以从ctypes
属性计算出来)。native_name
属性是函数在当前进程中可查找的符号。inspect_llvm()
方法返回编译该函数的 LLVM 模块的 IR。预计native_name
属性对应于 LLVM IR 中函数的名称。
装饰器的一般签名是 cfunc(signature, **options)
。
signature
必须使用 Numba 类型指定函数的参数类型和返回类型。与 @jit
不同,返回类型不能省略。
options
是仅限关键字的参数,用于指定编译选项。我们预计标准 @jit
选项(nopython
、forceobj
、cache
)可以与 @cfunc
配合使用。
从 Numba 编译的函数中调用
虽然预期用途是将回调函数的地址传递给需要函数指针的外部 C 代码,但应该可以从 Numba 编译的函数中调用 C 回调函数。
传递数组数据
C 或 C++ 使用的原生平台 ABI 没有 Numpy 中那种带形状的数组概念。一个常见的解决方案是传递一个原始数据指针和一个或多个大小参数(取决于维度)。Numba 必须提供一种方法,以便在回调函数内部重新构建此数据的数组视图。
from numba import cfunc, carray
from numba.types import float64, CPointer, void, intp
# A callback with the C signature `void(double *, double *, size_t)`
@cfunc(void(CPointer(float64), CPointer(float64), intp))
def invert(in_ptr, out_ptr, n):
in_ = carray(in_ptr, (n,))
out = carray(out_ptr, (n,))
for i in range(n):
out[i] = 1 / in_[i]
carray
函数接受 (pointer, shape, dtype)
参数(dtype
是可选的),并返回一个 C 布局的数组视图,该视图基于数据 pointer,具有给定的 shape 和 dtype。pointer 必须是 ctypes 指针对象(而不是 Python 整数)。数组的维度对应于 shape 元组的长度。如果未给出 dtype,则数组的 dtype 对应于 pointer 所指向的类型。
farray
函数与此类似,不同之处在于它返回一个 F 布局的数组视图。
错误处理
C 语言中没有标准化的错误报告机制。不幸的是,Numba 目前不处理 try..except
块,这使得用户更难实现所需的错误报告方案。本提案目前的立场是,让用户在必要时防范无效参数,并采取一切必要措施通知调用者错误。
根据用户反馈,我们稍后可以添加对某些错误报告方案的支持,例如根据是否引发异常返回整数错误代码,或设置 errno
。