如何将Numpy数组传递给cffi函数并获取返回值?

31

我正在使用Python和Numpy开发一个音频算法。现在我想通过用C来实现其中的一部分来加速该算法。以前,我曾使用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个回答

27

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

关于您的第三个问题:

我认为ffi数组没有提供给numpy访问其内部缓冲区所需的必要信息。因此numpy尝试将其转换为浮点数,但失败了。

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

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

18

关于这个问题的最新更新:现代版本的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)

16

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


1

如果你从cffi得到了一个扁平的结果数组,你也可以通过numpy使用给定的步幅来重新调整数组形状,像这样:

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

or

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

例如,verts应该看起来像这样:[[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接