如何将Numpy数组传递给cffi函数以及如何将其返回?

时间:2013-04-29 10:22:26

标签: python arrays numpy python-cffi

我正在使用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)

但是,我想改进这段代码:

  1. 有没有办法直接访问numpy数组的底层浮点缓冲区而不复制它们?
  2. ffi.buffer非常便于快速转换为C数组的内容为Numpy数组。有没有一种方法可以快速将numpy数组转换为C数组而不复制单个元素?
  3. 对于某些应用程序,float_in[0:16] = arr_in[0:16]是访问数据的便捷方式。相反,arr_out[0:16] = float_out[0:16]不起作用。为什么不呢?

5 个答案:

答案 0 :(得分:21)

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尝试将其转换为失败的浮点数。

我能想到的最佳解决方案是先将其转换为列表:

float_in[0:16] = list(arr_in[0:16])

答案 1 :(得分:13)

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是结构化的,你将不得不考虑它的形状和步幅。但是,如果它完全平坦,这就足够了。

答案 2 :(得分:7)

对此的更新:现代版本的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)

答案 3 :(得分:1)

从cffi获得平坦结果数组后, 您还可以通过numpy使用给定的步幅重塑数组,如下所示:

a=np.ones(24); a.shape = (2, 3, 4)

a=np.ones(24); b = a.reshape(2, 3, 4)

例如,如果您想要嵌套列表以进行进一步的python处理(例如在Blenders sverchok插件中),这将很有帮助

更复杂的示例:

假设您要拥有一个包含三个浮点数的顶点列表,并创建了一个cdata浮点数组,如下所示:

 cverts = ffi.new("float [][3]", nverts * num)

作为函数的输出参数:

lib.myfunction(... other input...., num, nverts, cverts)

将此版本列表切成 nverts 个版本的 num 个子列表,然后可以分别执行以下操作:

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()

版本应类似于[[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]

答案 4 :(得分:1)

从CFFI 1.12版本开始,您可以通过一次调用FFI.from_buffer来创建指向NumPy数组的适当类型的指针:

array = np.zeros(16, dtype=np.float32)
pointer = ffi.from_buffer("float[]", array)
写入此指针后面的数组的

C代码将直接更改原始NumPy数组。无需“得出结果”。

如果数组可能没有numpy.ascontiguousarray的内存布局,则可能要在将C_CONTIGUOUS传递到缓冲区之前先调用它。