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]它不起作用。为什么不?
4

5 回答 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

对于您的问题 3:

我认为 ffi 数组不提供 numpy 访问其内部缓冲区的必要信息。所以 numpy 尝试将其转换为失败的浮点数。

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

float_in[0:16] = list(arr_in[0:16])
于 2013-04-30T00:17:01.697 回答
16

对此的更新:现代版本的 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)
于 2016-08-31T07:33:06.510 回答
15

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

于 2013-05-06T07:13:26.317 回答
2

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

array = np.zeros(16, dtype=np.float32)
pointer = ffi.from_buffer("float[]", array)

写入此指针后面的数组的 C 代码将直接改变原始 NumPy 数组。没有必要“把结果拿出来”。

numpy.ascontiguousarray如果数组可能没有C_CONTIGUOUS内存布局,您可能需要在将其传递给缓冲区之前调用。

于 2020-10-18T17:32:08.723 回答
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 插件中),这将很有帮助

更复杂的例子:

假设你想要一个顶点列表列表,每个顶点有 3 个浮点数,并创建了一个 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]]]这样。

于 2019-01-05T22:42:58.227 回答