从结构的 C 数组到带有 ctypes 的 NumPy 数组的高效转换

Efficient conversion from C array of struct to NumPy array with ctypes

本文关键字:数组 NumPy ctypes 高效 转换 结构      更新时间:2023-10-16

在这个问题之后,我尝试将C++ DLL 与Cython一起使用,以及适合我的情况的教程,但从未奏效,我决定使用ctypes.由于繁重的 SO 浏览,我现在使用 ctypes 在我的 DLL 中成功调用了我感兴趣的函数。我现在面临着使用 Python 中结构数组的结果的问题。

此 C 函数如下所示:

void myfun(
double         a,
//...more double parameters
int            max_iter,
int *          nb_iter,
myStruct *     res_arr,
bool *         ok
);

myStruct定义如下:

typedef struct  {
double dat;
int    k;
int    m;
// ... more int
double b;
double v;
//...more double
} myStruct;

我通过以下 Python 代码调用此函数:

import ctypes
lib = ctypes.CDLL('PATH_TO_DLL\lib.dll')
myFunPy = getattr(lib,"?myFun@@YANNNNN_BUNCH_OF_Ns_NNNHPEAHPEAUmyStruct@@PEA_N@Z") # name found through dumpbin.exe (due to C++)
class myStruct(ctypes.Structure):
_fields_ = [("k", ctypes.c_int),
("m", ctypes.c_int),
#...more int parameters
("b", ctypes.c_double),
("v", ctypes.c_double)
#...more double parameters
]
myFunPy.argtypes = [ctypes.c_double,
// ... more double parameters
ctypes.c_int,
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(myStruct),
ctypes.POINTER(ctypes.c_bool)]
myFunPy.restype = ctypes.c_void_p
max_iter = 10000
a = ctypes.c_double(0.1)
// ... more double parameters definitions
nb_iter = ctypes.c_int(0) # value doesn't matter, it is initialized in myFun
ok = ctypes.c_bool(True)
res_arr = (myStruct * max_iter)()
myFunPy(a, ..., max_iter, ctypes.byref(nb_iter), res_arr, ctypes.byref(ok))

现在myFun修改res_arr这是一个结构数组,从上面的代码中可以看出。 正是

<__main__.myStruct_Array_10000 at 0x97966c8>)

在上面显示的代码之后,但我无法理解如何将其转换为 NumPy 数组以供将来有效使用。

当然,我可以像这里所示使用for field, _ in struct._fields_之类的东西来做循环,但这不是重点,因为我使用 DLL 来加快计算速度(我真的看到了执行时间的差异(。res_arr的范围从 200 kb 到 1 Mb,有数万行和几十列,所以我确信有一种方法不用循环来遍历所有内容,但我不知道如何做到这一点。

似乎如果它不是一个结构数组,它会更容易。有一些SO问题(也在这里,这里,这里和这里(接近这个主题,但它要么是关于只转换一个结构,只是一个数组,要么是接近但从来没有像我一样的东西,而且我没有成功地适应这些解决方案,所以也许有一种方法可以基于答案,但无论如何我都是耳朵。

我们遇到了几乎相同的问题,但就我而言,我使用的是 CUDA DLL,所以我的编译器nvcc。但我相信这也可以通过普通的g++编译器来完成。无论如何,以下是我为将结构数组从我的 CPP 文件转换为可用的 Python 列表/数组而执行的步骤。我不会通过你的代码;相反,我只举一个例子,可以在这里找到:https://github.com/jcbacong/python-cpp.git

但重要步骤总结如下:

  1. 使用包含函数extern "C"声明的必要头文件创建一个.cpp文件。在我的.cpp文件中,我返回了一个结构数组,而不是返回一个void

  2. 使用编译器创建.dll文件。同样,就我而言,它是nvcc.我通过 github 帐户链接的示例代码是使用nvcc编译的。

  3. 在.py文件中:

    3.1 创建一个带有ctypes.Structure的 Python 类,以便在 .cpp/.h 文件中复制结构定义。

    3.2 使用argtype/restype初始化输入/输出。由于我的.cpp函数返回一个结构数组,因此restypectypes.Pointer(<your Python Class(ctypes.Structure)>)给出。

    3.3 我将所有输入转换为可读的 ctypes。在我的.py文件中调用函数后,可以使用(results = _results[:ARRAY_SIZE](将生成的结构数组(示例中_results(转换为Python列表。

我希望这有帮助!!