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
,