如何将 Numpy 数组传递给 cffi 函数以及如何将其取出?
Posted
技术标签:
【中文标题】如何将 Numpy 数组传递给 cffi 函数以及如何将其取出?【英文标题】:How to pass a Numpy array into a cffi function and how to get one back out? 【发布时间】:2013-04-22 23:29:35 【问题描述】:我正在使用 Python 和 Numpy 开发音频算法。现在我想通过在 C 中实现它的一部分来加速该算法。过去,I have done this using cython。现在我想用新的cffi 做同样的事情。
出于测试目的,我写了一个简单的 C 函数:
void copy(float *in, float *out, int len)
for (int i=0; i<len; i++)
out[i] = in[i];
现在我想创建两个 numpy 数组并由这个函数处理。 我想出了一个办法:
import numpy as np
from cffi import FFI
ffi = FFI()
ffi.cdef("void copy(float *in, float *out, int len);")
C = ffi.dlopen("/path/to/copy.dll")
float_in = ffi.new("float[16]")
float_out = ffi.new("float[16]")
arr_in = 42*np.ones(16, dtype=np.float32)
float_in[0:16] = arr_in[0:16]
C.copy(float_in, float_out, 16)
arr_out = np.frombuffer(ffi.buffer(float_out, 16*4), dtype=np.float32)
但是,我想改进这段代码:
-
有没有办法直接访问 numpy 数组的底层浮点缓冲区而不复制它们?
ffi.buffer
非常方便快速将 C 数组的内容转换为 Numpy 数组。有没有一种等效的方法可以在不复制单个元素的情况下快速将 numpy 数组转换为 C 数组?
对于某些应用程序,float_in[0:16] = arr_in[0:16]
是一种访问数据的便捷方式。相反,arr_out[0:16] = float_out[0:16]
不起作用。为什么不呢?
【问题讨论】:
【参考方案1】:ndarray的ctypes
属性可以与ctypes模块进行交互,例如ndarray.ctypes.data
是数组的数据地址,可以将其强制转换为float *
指针,
然后将指针传递给 C 函数。
import numpy as np
from cffi import FFI
ffi = FFI()
ffi.cdef("void copy(float *in, float *out, int len);")
C = ffi.dlopen("ccode.dll")
a = 42*np.ones(16, dtype=np.float32)
b = np.zeros_like(a)
pa = ffi.cast("float *", a.ctypes.data)
pb = ffi.cast("float *", b.ctypes.data)
C.copy(pa, pb, len(a))
print b
对于你的问题 3:
我认为 ffi 数组不提供 numpy 访问其内部缓冲区的必要信息。所以 numpy 尝试将其转换为失败的浮点数。
我能想到的最好的解决方案是先将其转换为列表:
float_in[0:16] = list(arr_in[0:16])
【讨论】:
【参考方案2】:numpy 数组中的数据可以通过它的数组接口访问:
import numpy as np
import cffi
ffi = cffi.FFI()
a = np.zeros(42)
data = a.__array_interface__['data'][0]
cptr = ffi.cast ( "double*" , data )
现在您有了一个 cffi 指针类型,您可以将其传递给您的复制例程。请注意,这是一种基本方法; numpy 数组可能不会在平面内存中包含它们的数据,所以如果你的 ndarray 是结构化的,你将不得不考虑它的形状和步幅。不过,如果一切都是平的,这就足够了。
【讨论】:
【参考方案3】:对此的更新:现代版本的 CFFI 具有 ffi.from_buffer()
,它将任何缓冲区对象(如 numpy 数组)转换为 char *
FFI 指针。你现在可以直接做:
cptr = ffi.cast("float *", ffi.from_buffer(my_np_array))
或直接作为调用的参数(char *
自动转换为 float *
):
C.copy(ffi.from_buffer(arr_in), ffi.from_buffer(arr_out), 16)
【讨论】:
【参考方案4】:从 CFFI 1.12 版开始,您可以通过一次调用 FFI.from_buffer
来创建指向 NumPy 数组的适当类型的指针:
array = np.zeros(16, dtype=np.float32)
pointer = ffi.from_buffer("float[]", array)
写入此指针后面的数组的 C 代码将直接改变原始 NumPy 数组。没有必要“把结果拿出来”。
如果数组可能没有C_CONTIGUOUS
内存布局,您可能需要在将其传递到缓冲区之前调用numpy.ascontiguousarray
。
【讨论】:
【参考方案5】:从 cffi 得到一个平面结果数组后, 您还可以通过 numpy 以给定的步幅重塑数组,如下所示:
a=np.ones(24); a.shape = (2, 3, 4)
或
a=np.ones(24); b = a.reshape(2, 3, 4)
例如,如果您想要嵌套列表以进行进一步的 Python 处理(例如在 blenders sverchok 插件中),这将很有帮助
更复杂的例子:
假设你想要一个顶点列表列表,每个顶点有 3 个浮点数,并创建了一个 cdata 浮点数组,如下所示:
cverts = ffi.new("float [][3]", nverts * num)
作为函数的输出参数,例如:
lib.myfunction(... other input...., num, nverts, cverts)
将此顶点列表切割成 num 个 nverts 个顶点的子列表,然后您可以执行以下操作:
flat_size = 4 * 3 * nverts * num
verts = np.frombuffer(ffi.buffer(cverts, flat_size), dtype=np.float32)
verts.shape = (num, nverts, 3)
verts = verts.tolist()
verts 应该看起来像 [[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]
然后。
【讨论】:
以上是关于如何将 Numpy 数组传递给 cffi 函数以及如何将其取出?的主要内容,如果未能解决你的问题,请参考以下文章
将 2d numpy 数组传递给 C++ 时出现 TypeError
将 numpy 数组传递给 c++ 函数并返回 numpy 数组作为输出的最有效方法是啥?