python - 获取指向 Numpy/Numpypy 数据的指针的便携/快速方法

标签 python api numpy ctypes pypy

我最近尝试了 PyPy 并且对这种方法很感兴趣。我有很多 Python 的 C 扩展,它们都使用 PyArray_DATA() 来获取指向 numpy 数组数据部分的指针。不幸的是,PyPy 似乎没有在他们的 cpyext 模块中为他们的 numpypy 数组导出等效项,所以我尝试按照他们网站上的建议使用 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,两者都非常慢,并且是我应用程序的完全瓶颈!有没有一种快速便携的方法来获取这个指针?或者 PyPy 是否有等效的 PyArray_DATA()(可能未记录)?

最佳答案

我仍然没有找到一个完全令人满意的解决方案,但是在 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 之后,只要他们(非常顽固),我就看不到它的 future 拒绝支持完整的 CPython API。

更新

我发现 ctypes.cast() 在引入 accel._get_ptr() 后成为了瓶颈。可以通过将接口(interface)中的所有指针声明为 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(),ctypes 的速度实际上可以与原生 CPython 扩展相媲美。所以我只需要等到有人用 ctypes 重写 h5pymatplotlibscipy 就可以尝试 PyPy 做任何严肃的事情......

关于python - 获取指向 Numpy/Numpypy 数据的指针的便携/快速方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15210429/

相关文章:

python - 如何修复 PyCharm 上 anaconda 的导入错误?

python - BeautifulSoup 解析 'findAll' 运行错误

python - 根据项目数计算获取页数

python - 无法使用 pyplot for ndarray 绘制双条、条形图

python - 用 Pandas 加载不同列号的csv

带有本地套接字的 Python PaaS

javascript - Youtube API 全屏问题

excel - 如何在 Angular 8 中下载 excel 文件作为 API 响应

web-services - 什么是 RESTful api 以及它与 Web 服务的区别?

python - 在 pandas 或 numpy 中,我们可以在行上设置一个标志来进行向量化并将其用于下一行计算