Cython: понимание типизированного представления памяти с помощью косвенной непрерывной схемы памяти

Я хочу больше узнать об удивительных типизированных представлениях Cython и структуре памяти indirect_contiguous,

Согласно документации indirect_contiguous используется, когда "список указателей является смежным".

Также есть пример использования:

# contiguous list of pointers to contiguous lists of ints
cdef int[::view.indirect_contiguous, ::1] b

Так что поправьте меня, если я ошибаюсь, но я предполагаю, что "непрерывный список указателей на непрерывные списки целых" означает что-то вроде массива, созданного следующим фиктивным кодом C++:

// we want to create a 'contiguous list of pointers to contiguous lists of ints'

int** array;
// allocate row-pointers
// This is the 'contiguous list of pointers' related to the first dimension:
array = new int*[ROW_COUNT]

// allocate some rows, each row is a 'contiguous list of ints'
array[0] = new int[COL_COUNT]{1,2,3}

Так что, если я правильно понимаю, то в моем коде Cython должно быть возможно получить представление о памяти от int** как это:

cdef int** list_of_pointers = get_pointers()
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,COL_COUNT:1]> list_of_pointers

Но я получаю ошибки компиляции:

cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,:COL_COUNT:1]> list_of_pointers
                                                                                                        ^                                                                                                                              
------------------------------------------------------------

memview_test.pyx:76:116: Pointer base type does not match cython.array base type

что я сделал не так? Я пропускаю какие-либо приведения или я неправильно понял концепцию косвенного-непрерывного?

1 ответ

Решение

Давайте установим запись прямо: типизированное представление памяти может использоваться только с объектами, которые реализуют буферный протокол.

Сырые C-указатели, очевидно, не реализуют буферный протокол. Но вы можете спросить, почему работает что-то вроде следующего быстрого и грязного кода:

%%cython    
from libc.stdlib cimport calloc
def f():
    cdef int* v=<int *>calloc(4, sizeof(int))
    cdef int[:] b = <int[:4]>v
    return b[0] # leaks memory, so what?

Здесь указатель (v) используется для построения типизированного представления памяти (b). Тем не менее, есть еще кое-что, скрывающееся под капотом (как можно увидеть в цитонизированном c-файле):

  • Cython-массив (т.е. cython.view.array), который оборачивает необработанный указатель и может предоставить его через буферный протокол
  • этот массив используется для создания типизированного представления памяти.

Ваше понимание того, что view.indirect_contiguous используется для правильно - это именно то, что вы хотите. Однако проблема в том, view.array, который просто не может обработать этот тип макета данных.

view.indirect а также view.indirect_contiguous соответствовать PyBUF_INDIRECT в языке протокола-буфера и для этого поля suboffsets должны содержать некоторые значимые значения (т.е. >=0 для некоторых размеров). Однако, как можно видеть в исходном коде view.array вообще не имеет этого члена - нет способа, которым он вообще может представлять сложную структуру памяти!

Куда это нас оставляет? Как указывалось @chrisb и @DavidW в вашем другом вопросе, вам придется реализовать оболочку, которая может предоставлять вашу структуру данных через буфер протокола.

В Python есть структуры данных, которые используют непрямой макет памяти - в первую очередь PIL-массивы. Хорошая отправная точка, чтобы понять, как suboffsets должны работать этот кусок документации:

void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
                       Py_ssize_t *suboffsets, Py_ssize_t *indices) {
    char *pointer = (char*)buf;    // A
    int i;
    for (i = 0; i < ndim; i++) {
        pointer += strides[i] * indices[i]; // B
        if (suboffsets[i] >=0 ) {
            pointer = *((char**)pointer) + suboffsets[i];  // C
        }
    }
    return (void*)pointer;  // D
}

В твоем случае strides а также offsets было бы

  • strides=[sizeof(int*), sizeof(int)] (т.е. [8,4] на обычном x86_64 машины)
  • offsets=[0,-1] т.е. только первое измерение является косвенным.

Получение адреса элемента [x,y] будет происходить следующим образом:

  • в соответствии A, pointer установлен в buf давайте предположим BUF,
  • первое измерение:
    • в соответствии B, pointer становится BUF+x*8 и указывает на местоположение указателя на x-ю строку.
    • так как suboffsets[0]>=0 Разыменяем указатель в строке C и таким образом это показывает, чтобы обратиться ROW_X - начало x-го ряда.
  • второе измерение:
    • в соответствии B мы получаем адрес y использование элемента strides т.е. pointer=ROW_X+4*y
    • второе измерение является прямым (сигнализируется suboffset[1]<0), поэтому разыменование не требуется.
  • мы сделали, pointer указывает на нужный адрес и возвращается в строке D,
Другие вопросы по тегам