How to pass a Numpy array into a cffi function and

2019-02-01 20:08发布

I am developing an audio algorithm using Python and Numpy. Now I want to speed up that algorithm by implementing a part of it in C. In the past, I have done this using cython. Now I want to do the same thing using the new cffi.

For testing purposes, I wrote a trivial C function:

void copy(float *in, float *out, int len) {
    for (int i=0; i<len; i++) {
        out[i] = in[i];
    }
}

Now I want to create two numpy arrays and have those be processed by this function. I figured out a way to do that:

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)

However, I would like to improve this code:

  1. Is there a way to directly access the underlying float buffers of the numpy arrays without copying them?
  2. ffi.buffer is very convenient for quickly converting to contents of a C array to a Numpy array. Is there an equivalent way for quickly converting a numpy array into a C array without copying the individual elements?
  3. For some applications, float_in[0:16] = arr_in[0:16] is a convenient way of accessing data. The opposite, arr_out[0:16] = float_out[0:16] does not work however. Why not?

4条回答
再贱就再见
2楼-- · 2019-02-01 20:38

the data in a numpy array can be accessed via it's array interface:

import numpy as np
import cffi
ffi = cffi.FFI()

a = np.zeros(42)
data = a.__array_interface__['data'][0]
cptr = ffi.cast ( "double*" , data )

now you have a cffi pointer type, which you can pass to your copy routine. note that this is a basic approach; numpy arrays may not contain their data in flat memory, so if your ndarray is structured, you will have to consider it's shape and strides. If it's all flat, though, this is sufficient.

查看更多
The star\"
3楼-- · 2019-02-01 20:48

After you got a flat result array from cffi, you also could reshape the array with given strides via numpy like this:

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

or

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

This is for example helpful if you want to have nested lists for further python processing (like in blenders sverchok addon)

More complex example:

Say you want to have a list of lists of vertices with 3 floats each, and have created a cdata float array like this:

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

as output parameter for a function like:

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

Cutting this list of verts into num sub-lists of nverts verts each you could do as follows then:

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 should look for example like [[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]] then.

查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-02-01 20:51

An update to this: modern versions of CFFI have ffi.from_buffer(), which turns any buffer object (like a numpy array) to a char * FFI pointer. You can now do directly:

cptr = ffi.cast("float *", ffi.from_buffer(my_np_array))

or directly as arguments to the call (the char * is casted automatically to float *):

C.copy(ffi.from_buffer(arr_in), ffi.from_buffer(arr_out), 16)
查看更多
做自己的国王
5楼-- · 2019-02-01 20:59

The ctypes attribute of ndarray can interact with the ctypes module, for example, ndarray.ctypes.data is the data address of the array, you can cast it to a float * pointer, and then pass the pointer to the C function.

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

For your question 3:

I think ffi array doesn't provide numpy the necessary information to access it's inner buffer. So numpy try to convert it to a float number which failed.

The best solution I can thinks is convert it to list first:

float_in[0:16] = list(arr_in[0:16])
查看更多
登录 后发表回答