如何获取 ctypes 指针数据的格式字符串
How to get format-string for data of a ctypes-pointer
给定一个 ctypes 指针,例如 double**
:
import ctypes
data=(ctypes.POINTER(ctypes.c_double)*4)() # results in [NULL, NULL, NULL, NULL]
是否可以得到一个描述data
内存布局的format string?
现在,我创建一个内存视图来获取这些信息,感觉有点傻:
view=memoryview(data)
print(view.format) # prints: &<d
是否有更直接且开销更少的方法?也许通过使用 C-API?
可以用有意义的值填充 data
,如果这有任何帮助的话:
import ctypes
data=(ctypes.POINTER(ctypes.c_double)*2)(
(ctypes.c_double*2)(1.0,2.0),
(ctypes.c_double*1)(3.0))
# results in [
# ptr0 -> [1,2],
# ptr1 -> [3]
# ]
print(data[1][0]) # prints 3.0
似乎没有什么比 memoryview(data).format
从根本上更好的了。但是,可以通过使用 C-API.
稍微加快速度
格式字符串(它扩展了 struct format-string-syntax 描述的 in PEP3118) is calculated recursively and is stored in the format
-member of the StgDictObject
-object, which can be found in the tp_dict
-字段
ctypes-arrays/pointers:
typedef struct {
PyDictObject dict; /* first part identical to PyDictObject */
...
/* pep3118 fields, pointers neeed PyMem_Free */
char *format;
int ndim;
Py_ssize_t *shape;
...
} StgDictObject;
此 format
字段仅在递归计算期间和 a buffer is exported 时访问 - 这就是 memoryview
获取此信息的方式:
static int PyCData_NewGetBuffer(PyObject *myself, Py_buffer *view, int flags)
{
...
/* use default format character if not set */
view->format = dict->format ? dict->format : "B";
...
return 0;
}
现在我们可以使用 C-API 填充缓冲区(无需创建实际的 memoryview
),此处在 Python:
中实现
%%cython
from cpython cimport buffer
def get_format_via_buffer(obj):
cdef buffer.Py_buffer view
buffer.PyObject_GetBuffer(obj, &view, buffer.PyBUF_FORMAT|buffer.PyBUF_ANY_CONTIGUOUS)
cdef bytes format = view.format
buffer.PyBuffer_Release(&view)
return format
这个版本比通过 memoryview
:
快 3 倍左右
import ctypes
c=(ctypes.c_int*3)()
%timeit get_format_via_buffer(c) # 295 ns ± 10.3
%timeit memoryview(c).format # 936 ns ± 7.43 ns
在我的机器上,调用 def
函数大约需要 160 ns 的开销,创建字节对象大约需要 50
ms。
即使由于不可避免的开销而进一步优化它没有多大意义,但至少在理论上仍然对如何加速它感兴趣。
如果真的想削减填写 Py_buffer-struct 的成本,那么没有干净的方法:ctypes-module 不是 Python-C-[= 的一部分56=](它不在include-directory中),所以前进的方法是重复the solution Cython uses with the array.array
, i.e. hardcoding the memory layout of the object (which makes this solution brittle because the memory-layout of StgDictObject
可以不同步)。
这里使用 Cython,没有错误检查:
%%cython -a
from cpython cimport PyObject
# emulate memory-layout (i.e. copy definitions from ctypes.h)
cdef extern from *:
"""
#include <Python.h>
typedef struct _ffi_type
{
size_t size;
unsigned short mem[2];
struct _ffi_type **elements;
} ffi_type;
typedef struct {
PyDictObject dict; /* first part identical to PyDictObject */
Py_ssize_t size[3]; /* number of bytes,alignment requirements,number of fields */
ffi_type ffi_type_pointer;
PyObject *proto; /* Only for Pointer/ArrayObject */
void *setfunc[3];
/* Following fields only used by PyCFuncPtrType_Type instances */
PyObject *argtypes[4];
int flags; /* calling convention and such */
/* pep3118 fields, pointers neeed PyMem_Free */
char *format;
int ndim;
} StgDictObject;
"""
ctypedef struct StgDictObject:
char *format
def get_format_via_hack(obj):
cdef PyObject *p =<PyObject *>obj
cdef StgDictObject *dict = <StgDictObject *>(p.ob_type.tp_dict)
return dict.format
而且速度很快:
%timeit get_format_via_hack(c) # 243 ns ± 14.5 ns
给定一个 ctypes 指针,例如 double**
:
import ctypes
data=(ctypes.POINTER(ctypes.c_double)*4)() # results in [NULL, NULL, NULL, NULL]
是否可以得到一个描述data
内存布局的format string?
现在,我创建一个内存视图来获取这些信息,感觉有点傻:
view=memoryview(data)
print(view.format) # prints: &<d
是否有更直接且开销更少的方法?也许通过使用 C-API?
可以用有意义的值填充 data
,如果这有任何帮助的话:
import ctypes
data=(ctypes.POINTER(ctypes.c_double)*2)(
(ctypes.c_double*2)(1.0,2.0),
(ctypes.c_double*1)(3.0))
# results in [
# ptr0 -> [1,2],
# ptr1 -> [3]
# ]
print(data[1][0]) # prints 3.0
似乎没有什么比 memoryview(data).format
从根本上更好的了。但是,可以通过使用 C-API.
格式字符串(它扩展了 struct format-string-syntax 描述的 in PEP3118) is calculated recursively and is stored in the format
-member of the StgDictObject
-object, which can be found in the tp_dict
-字段
ctypes-arrays/pointers:
typedef struct {
PyDictObject dict; /* first part identical to PyDictObject */
...
/* pep3118 fields, pointers neeed PyMem_Free */
char *format;
int ndim;
Py_ssize_t *shape;
...
} StgDictObject;
此 format
字段仅在递归计算期间和 a buffer is exported 时访问 - 这就是 memoryview
获取此信息的方式:
static int PyCData_NewGetBuffer(PyObject *myself, Py_buffer *view, int flags)
{
...
/* use default format character if not set */
view->format = dict->format ? dict->format : "B";
...
return 0;
}
现在我们可以使用 C-API 填充缓冲区(无需创建实际的 memoryview
),此处在 Python:
%%cython
from cpython cimport buffer
def get_format_via_buffer(obj):
cdef buffer.Py_buffer view
buffer.PyObject_GetBuffer(obj, &view, buffer.PyBUF_FORMAT|buffer.PyBUF_ANY_CONTIGUOUS)
cdef bytes format = view.format
buffer.PyBuffer_Release(&view)
return format
这个版本比通过 memoryview
:
import ctypes
c=(ctypes.c_int*3)()
%timeit get_format_via_buffer(c) # 295 ns ± 10.3
%timeit memoryview(c).format # 936 ns ± 7.43 ns
在我的机器上,调用 def
函数大约需要 160 ns 的开销,创建字节对象大约需要 50
ms。
即使由于不可避免的开销而进一步优化它没有多大意义,但至少在理论上仍然对如何加速它感兴趣。
如果真的想削减填写 Py_buffer-struct 的成本,那么没有干净的方法:ctypes-module 不是 Python-C-[= 的一部分56=](它不在include-directory中),所以前进的方法是重复the solution Cython uses with the array.array
, i.e. hardcoding the memory layout of the object (which makes this solution brittle because the memory-layout of StgDictObject
可以不同步)。
这里使用 Cython,没有错误检查:
%%cython -a
from cpython cimport PyObject
# emulate memory-layout (i.e. copy definitions from ctypes.h)
cdef extern from *:
"""
#include <Python.h>
typedef struct _ffi_type
{
size_t size;
unsigned short mem[2];
struct _ffi_type **elements;
} ffi_type;
typedef struct {
PyDictObject dict; /* first part identical to PyDictObject */
Py_ssize_t size[3]; /* number of bytes,alignment requirements,number of fields */
ffi_type ffi_type_pointer;
PyObject *proto; /* Only for Pointer/ArrayObject */
void *setfunc[3];
/* Following fields only used by PyCFuncPtrType_Type instances */
PyObject *argtypes[4];
int flags; /* calling convention and such */
/* pep3118 fields, pointers neeed PyMem_Free */
char *format;
int ndim;
} StgDictObject;
"""
ctypedef struct StgDictObject:
char *format
def get_format_via_hack(obj):
cdef PyObject *p =<PyObject *>obj
cdef StgDictObject *dict = <StgDictObject *>(p.ob_type.tp_dict)
return dict.format
而且速度很快:
%timeit get_format_via_hack(c) # 243 ns ± 14.5 ns