Как получить строку формата для данных указателя ctypes - PullRequest
0 голосов
/ 28 января 2019

При наличии указателя ctypes, например, double**:

import ctypes
data=(ctypes.POINTER(ctypes.c_double)*4)()   #  results in [NULL, NULL, NULL, NULL]

возможно ли получить строку формата , которая описывает структуру памяти data?

Прямо сейчас я создаю обзор памяти, чтобы получить эту информацию, которая выглядит несколько глупо:

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      

1 Ответ

0 голосов
/ 31 января 2019

Кажется, что нет ничего принципиально лучше, чем 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 
...