我仍然没有找到一个完全令人满意的解决方案,但是仍然可以做一些事情来获得在 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 扩展相媲美。所以我只需要等到有人重写h5py
,matplotlib
并且scipy
使用 ctypes 才能尝试 PyPy 处理任何严重的事情......