Кажется, что нет ничего принципиально лучше, чем memoryview(data).format
.Однако это можно немного ускорить, используя C-API.
Формат-строка (который расширяет синтаксис struct format-string-syntax, как описано в PEP3118 ), рассчитываетсярекурсивно и сохраняется в format
-члене StgDictObject
-объекта , который можно найти в tp_dict
-поле ctypes-массивов / указателей:
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
доступно только во время рекурсивного вычисления, и когда буфер экспортируется - таким образом 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
Эта версия примерно в 3 раза быстрее, чем через 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
На моей машине около 160 нс приходится на вызов def
-функции и около 50
мс для создания объекта байтов.
Даже если нет смысла оптимизировать его дальше из-за неизбежных накладных расходов, по-прежнему существует, по крайней мере, теорияЭтический интерес к тому, как это можно ускорить.
Если кто-то действительно хочет сбрить также стоимость заполнения Py_buffer-структуры, то нет чистого способа: модуль ctypes не является частью Python-C-API (его нет в каталоге include), поэтому путь вперед состоит в том, чтобы повторить решение, которое Cython использует с array.array
, то есть жесткое кодирование структуры памяти объекта (что делает эторешение хрупкое, потому что макет памяти 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