8

我最近尝试过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()for PyPy (可能未记录)?

4

2 回答 2

4

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

对于这种开销,在 Python 级别上没有什么可以做的,但是可以在 C 级别上编写一个微模块来绕过大部分开销:

#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,并导入为

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

在 PyPy 上,from accel import _get_ptr将失败并get_ptr回退到get_ptr_array与 Numpypy 一起使用的 。

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

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

更新

我发现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()来避免强制转换。ndarray.ctypes._data以下是从 Python 调用重量级和轻量级 C 函数的一些计时结果:

                             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()没有ctypes.cast()s,ctypes 的速度实际上都可以与原生 CPython 扩展相媲美。所以我只需要等到有人重写h5pymatplotlib并且scipy使用 ctypes 才能尝试 PyPy 处理任何严重的事情......

于 2013-03-29T10:07:31.993 回答
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. 查看文件夹中生成的 C++ 代码
于 2013-03-28T09:47:27.440 回答