以便携/快速方式获取指向Numpy / Numpypy数据的指针

时间:2013-03-04 20:24:02

标签: python api numpy ctypes pypy

我最近尝试了PyPy并对这种方法很感兴趣。我有许多Python的C扩展,它们都使用PyArray_DATA()来获取指向numpy数组的数据部分的指针。不幸的是,PyPy似乎没有在numpypy模块中导出其cpyext数组的等效项,因此我尝试按照其网站上的建议使用ctypes。这推动了获取Python级别指针的任务。

似乎有两种方式:

import ctypes as C
p_t = C.POINTER(C.c_double)

def get_ptr_ctypes(x):
    return x.ctypes.data_as(p_t)

def get_ptr_array(x):
    return C.cast(x.__array_interface__['data'][0], p_t)

只有第二个适用于PyPy,因此为了兼容性,选择很明确。对于CPython来说,两者都很慢,我的应用程序完全成为瓶颈!是否有快速便携的方法来获取此指针?或者是否有相当于PyArray_DATA()的PyPy(可能没有文档)?

2 个答案:

答案 0 :(得分:4)

我仍然没有找到一个完全令人满意的解决方案,但是在CPython中有一些人可以做的事情来获得更少开销的指针。首先,上述两种方式都很慢的原因是.ctypes.__array_interface__都是按需属性,由array_ctypes_get()array_interface_get()设置在{ {1}}。第一个导入ctypes并创建一个numpy/numpy/core/src/multiarray/getset.c实例,而第二个创建一个新的字典,并在数据指针之外填充许多不必要的东西。

在Python级别上没有任何人可以做这个开销,但是可以在C级编写一个微模块来绕过大部分开销:

numpy.core._internal._ctypes

像往常一样编译为#include <Python.h> #include <numpy/arrayobject.h> PyObject *_get_ptr(PyObject *self, PyObject *obj) { return PyLong_FromVoidPtr(PyArray_DATA(obj)); } static PyMethodDef methods[] = { {"_get_ptr", _get_ptr, METH_O, "Wrapper to PyArray_DATA()"}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initaccel(void) { Py_InitModule("accel", methods); } 中的扩展名,并导入为

setup.py

在PyPy上,try: from accel import _get_ptr def get_ptr(x): return C.cast(_get_ptr(x), p_t) except ImportError: get_ptr = get_ptr_array 将失败,from accel import _get_ptr将回退到get_ptr,这与Numpypy一起使用。

就性能而言,对于轻量级C函数调用,get_ptr_array仍然比本机CPython扩展慢得多,后者基本上没有开销。它当然比上面的ctypes + accel._get_ptr()get_ptr_ctypes()快得多,因此对于中等重量的C函数调用,开销可能变得微不足道。

一个人已经获得了与PyPy的兼容性,虽然我不得不说在花费了相当多的时间来为我的科学计算应用程序评估PyPy之后,只要它们(非常固执地)我就不会看到它的未来拒绝支持完整的CPython API。

<强>更新

我发现get_ptr_array()现在成为引入ctypes.cast()后的瓶颈。通过将接口中的所有指针声明为accel._get_ptr(),可以摆脱强制转换。这就是我最终的结果:

ctypes.c_void_p

此处,def get_ptr_ctypes2(x): return x.ctypes._data def get_ptr_array(x): return x.__array_interface__['data'][0] try: from accel import _get_ptr as get_ptr except ImportError: get_ptr = get_ptr_array 通过直接访问隐藏的get_ptr_ctypes2()属性来避免广播。以下是从Python调用重量级和轻量级C函数的一些时序结果:

ndarray.ctypes._data

因此,对于 heavy C (few calls) light C (many calls) ctypes + get_ptr_ctypes(): 0.71 s 15.40 s ctypes + get_ptr_ctypes2(): 0.68 s 13.30 s ctypes + get_ptr_array(): 0.65 s 11.50 s ctypes + accel._get_ptr(): 0.63 s 9.47 s native CPython: 0.62 s 8.54 s Cython (no decorators): 0.64 s 9.96 s 而没有accel._get_ptr() s,ctypes的速度实际上与本机CPython扩展竞争。所以我只需要等到有人用ctypes重写ctypes.cast()h5pymatplotlib才能为任何严肃的事情尝试PyPy ......

答案 1 :(得分:0)

这可能不够回答,但希望是一个很好的暗示。我在我的代码的某些部分使用scipy.weave.inline()。我不太了解接口本身的速度,因为我执行的函数很重,只依赖于几个指针/数组,但对我来说似乎很快。也许你可以从scipy.weave代码中获得灵感,特别是来自attempt_function_call

https://github.com/scipy/scipy/blob/master/scipy/weave/inline_tools.py#L390

如果你想看一下scipy.weave生成的C ++代码,

  1. 从此处生成一个简单示例:http://docs.scipy.org/doc/scipy/reference/tutorial/weave.html

  2. 运行python脚本

  3. 获取scipy.weave缓存文件夹:

    import scipy.weave.catalog as ctl
    ctl.default_dir()
    Out[5]: '/home/user/.python27_compiled'
    
  4. 查看文件夹
  5. 中生成的C ++代码