如何将 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)

将此顶点列表切割成 numnverts 个顶点的子列表,然后您可以执行以下操作:

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 数组作为输出的最有效方法是啥?

如何正确地将 numpy 数组传递给 Cython 函数?

为啥 cffi 比 numpy 快这么多?

将带有字符串的结构化 numpy 数组传递给 cython 函数

SWIG:将 2d numpy 数组传递给 C 函数 f(double a[])