将一组NumPy数组传入C函数进行输入和输出

14

假设我们有一个C函数,它接受一组或多组输入数组,对其进行处理,并将输出写入一组输出数组。函数签名如下所示(其中count表示要处理的数组元素数):

void compute (int count, float** input, float** output)

我想通过ctypes从Python调用这个函数,并将其用于一组NumPy数组的转换。对于定义为一个输入/一个输出函数的函数,例如:
void compute (int count, float* input, float* output)

以下内容可以正常运作:

import ctypes
import numpy

from numpy.ctypeslib import ndpointer

lib = ctypes.cdll.LoadLibrary('./block.so')
fun = lib.compute
fun.restype = None
fun.argtypes = [ctypes.c_int,
                ndpointer(ctypes.c_float),
                ndpointer(ctypes.c_float)]

data = numpy.ones(1000).astype(numpy.float32)
output = numpy.zeros(1000).astype(numpy.float32)
fun(1000, data, output)

然而,我不知道如何为多个输入(和/或输出)创建对应的指针数组。有什么想法吗?

编辑:所以人们一直在想,compute如何知道要期望多少个数组指针(因为count指的是每个数组的元素数量)。实际上,这是硬编码的;给定的compute知道要期望多少个输入和输出。调用方的任务是验证inputoutput指向正确数量的输入和输出。这是一个将2个输入写入1个输出数组的示例compute

virtual void compute (int count, float** input, float** output) {
    float* input0 = input[0];
    float* input1 = input[1];
    float* output0 = output[0];
    for (int i=0; i<count; i++) {
        float fTemp0 = (float)input1[i];
        fRec0[0] = ((0.09090909090909091f * fTemp0) + (0.9090909090909091f * fRec0[1]));
        float fTemp1 = (float)input0[i];
        fRec1[0] = ((0.09090909090909091f * fTemp1) + (0.9090909090909091f * fRec1[1]));
        output0[i] = (float)((fTemp0 * fRec1[0]) - (fTemp1 * fRec0[0]));
        // post processing
        fRec1[1] = fRec1[0];
        fRec0[1] = fRec0[0];
    }
}

我无法影响compute的签名和实现。我可以验证(使用Python!)需要多少输入和输出。关键问题是如何为函数提供正确的argtypes,以及如何在NumPy中生成适当的数据结构(指向NumPy数组的指针数组)。


这个问题对你有帮助吗?(链接为:https://dev59.com/uHM_5IYBdhLWcg3w9oaG) - Mike
很遗憾,不是的。它更具体地针对NumPy和ctypes。尽管如此,谢谢。 - apl
你可能需要重写 compute 函数以将数据存储为平面结构。 - ilmiacs
compute函数是自动生成的,因此我在签名和实现方面几乎没有任何影响力。 - apl
如果numpy无法处理它,您将需要一个C包装器,其签名已经解决。涉及复制输入和输出数组。可行吗? - ilmiacs
显示剩余3条评论
2个回答

9

对于特定的Numpy数组,您可以使用以下方法:

import numpy as np
import ctypes

count = 5
size = 1000

#create some arrays
arrays = [np.arange(size,dtype="float32") for ii in range(count)] 

#get ctypes handles
ctypes_arrays = [np.ctypeslib.as_ctypes(array) for array in arrays]

#Pack into pointer array
pointer_ar = (ctypes.POINTER(C.c_float) * count)(*ctypes_arrays)

ctypes.CDLL("./libfoo.so").foo(ctypes.c_int(count), pointer_ar, ctypes.c_int(size))

C端的实现可能是这样的:

# function to multiply all arrays by 2
void foo(int count, float** array, int size)
{
   int ii,jj;
   for (ii=0;ii<count;ii++){
      for (jj=0;jj<size;jj++)
         array[ii][jj] *= 2;    
   }

}

4

C语言中,float**指向float*指针数组/表的第一个元素。

可以假设每个float*指向存储float值的数组/表的第一个元素。

您的函数声明有1个计数,但不清楚该计数适用于什么:

void compute (int count, float** input, float** output)
  • 大小为count x count的2D矩阵?
  • countfloat*数组,每个数组以某种方式终止,例如nan
  • null结尾的count个元素的float*数组(合理假设)?

请澄清您的问题,我会澄清我的答案 :-)

假设最后一个API解释是正确的,这是我的示例计算函数:

/* null-terminated array of float*, each points to count-sized array
*/
extern void compute(int count, float** in, float** out)
{
    while (*in)
    {
        for (int i=0; i<count; i++)
        {
            (*out)[i] = (*in)[i]*42;
        }
        in++; out++;
    }
}

样例计算函数的测试代码:

#include <stdio.h>
extern void compute(int count, float** in, float** out);

int main(int argc, char** argv)
{
#define COUNT 3
    float ina[COUNT] = { 1.5, 0.5, 3.0 };
    float inb[COUNT] = { 0.1, -0.2, -10.0 };
    float outa[COUNT];
    float outb[COUNT];
    float* in[] = {ina, inb, (float*)0};
    float* out[] = {outa, outb, (float*)0};

    compute(COUNT, in, out);

    for (int row=0; row<2; row++)
        for (int c=0; c<COUNT; c++)
            printf("%d %d %f %f\n", row, c, in[row][c], out[row][c]);
    return 0;
}

如何在Python中使用ctypes处理count等于10的float子数组和大小为2float*数组,其中包含1个实际子数组和NULL终止符:

import ctypes

innertype = ctypes.ARRAY(ctypes.c_float, 10)
outertype = ctypes.ARRAY(ctypes.POINTER(ctypes.c_float), 2)

in1 = innertype(*range(10))
in_ = outertype(in1, None)
out1 = innertype(*range(10))
out = outertype(out1, None)

ctypes.CDLL("./compute.so").compute(10, in_, out)

for i in range(10): print in_[0][i], out[0][i]

这里介绍了与ctypes相关的Numpy接口:http://www.scipy.org/Cookbook/Ctypes#head-4ee0c35d45f89ef959a7d77b94c1c973101a562f,你需要使用arr.ctypes.shape[:]、arr.ctypes.strides[:]和arr.ctypes.data,可能可以直接将其传递给compute函数。
以下是一个例子:
In [55]: a = numpy.array([[0.0]*10]*2, dtype=numpy.float32)

In [56]: ctypes.cast(a.ctypes.data, ctypes.POINTER(ctypes.c_float))[0]
Out[56]: 0.0

In [57]: ctypes.cast(a.ctypes.data, ctypes.POINTER(ctypes.c_float))[0] = 1234

In [58]: a
Out[58]: 
array([[ 1234.,     0.,     0.,     0.,     0.,     0.,     0.,     0.,
            0.,     0.],
       [    0.,     0.,     0.,     0.,     0.,     0.,     0.,     0.,
            0.,     0.]], dtype=float32)

这看起来很不错。你的解释当然是正确的:count 指的是 float* 数组中元素的数量。float** 包含的指针数量在 compute 生成时已知,因此 compute “知道”要期望多少个输入和输出(它是通过编译另一种语言生成的)。因此,不需要空终止符。我会修改问题。 - apl
ebarr的具体提示解决了问题;尽管如此,非常感谢您深入的解释,帮助澄清了问题,并教会了我很多关于接口的知识... - apl

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