【问题标题】:Passing a set of NumPy arrays into C function for input and output将一组 NumPy 数组传递给 C 函数用于输入和输出
【发布时间】:2012-12-29 18:38:28
【问题描述】:

假设我们有一个 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 是否指向正确数量的输入和输出。这是一个示例 compute 接受 2 个输入并写入 1 个输出数组:

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 数组的指针数组)。

【问题讨论】:

  • this question 有帮助吗?
  • 不幸的是,没有。它更特定于 NumPy 和 ctypes。不过,谢谢。
  • 可能你需要重写compute 来存储数据。
  • compute 函数是自动生成的,所以我对签名和实现的影响很小。
  • 你怎么知道在自动生成的函数中,插入了多少个数组?

标签: python c numpy ctypes ffi


【解决方案1】:

要专门使用 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;    
   }

}

【讨论】:

    【解决方案2】:

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

    大概每个float* 都指向float 值的表/数组中的第一个元素。

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

    void compute (int count, float** input, float** output)
    
    • 二维矩阵count x count 大小?
    • count 大小的 float* 数组每个都以某种方式终止,例如nan?
    • float* 每个 count 元素的空终止数组(合理假设)?

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

    假设最后一个 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 子数组和大小 2 float* 数组,包含 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 “知道”预期有多少输入和输出。 (它是通过编译另一种语言生成的。)因此,不需要空终止。我会修改问题。
    • ebarr 的具体提示成功了;尽管如此,非常感谢您的深入解释,这有助于澄清问题并教会了我很多关于接口的知识......
    猜你喜欢
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多