我正在使用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)
但是,我想改进这段代码:
ffi.buffer
非常便于快速转换为C数组的内容为Numpy数组。有没有一种方法可以快速将numpy数组转换为C数组而不复制单个元素?float_in[0:16] = arr_in[0:16]
是访问数据的便捷方式。相反,arr_out[0:16] = float_out[0:16]
不起作用。为什么不呢?答案 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
传递到缓冲区之前先调用它。