Cython - MemoryView динамического 2D C ++ Array - PullRequest
0 голосов
/ 23 декабря 2018

Цель: Получить представление памяти из двумерного массива C ++ с использованием Cython.

Небольшой фон:

У меня есть нативныйБиблиотека C ++, которая генерирует некоторые данные и возвращает их через char** в мир Cython.Массив инициализируется и работает в библиотеке примерно так:

struct Result_buffer{
    char** data_pointer;
    int length = 0;

    Result_buffer( int row_capacity) {
        data_pointer; = new char*[row_capacity];
        return arr;
    }

    // the actual data is appended row by row
    void append_row(char* row_data) {
         data_pointer[length] = row_data;
         length++;
    }     
}

Таким образом, мы в основном получаем массив вложенных подмассивов.

Примечания:
- каждая строка имеет одинаковое количество столбцов
- строки могут совместно использовать память, т. Е. Указывать на одни и те же row_data

Цель состоит в том, чтобы использовать этот массив спросмотр памяти предпочтительно без дорогостоящего копирования памяти.


Первый подход (не работает) :

Использование массивов Cython и просмотров памяти:

Вот .pyx-файл, который должен сгенерировать сгенерированные данные

from cython cimport view
cimport numpy as np
import numpy as np

[...]

def raw_data_to_numpy(self):

    # Dimensions of the source array
    cdef int ROWS = self._row_count
    cdef int COLS = self._col_count

    # This is the array from the C++ library and is created by 'create_buffer()'
    cdef char** raw_data_pointer = self._raw_data

    # It only works with a pointer to the first nested array
    cdef char* pointer_to_0 = raw_data_pointer[0]

    # Now create a 2D Cython array
    cdef view.array cy_array = <char[:ROWS, :COLS]> pointer_to_0

    # With this we can finally create our NumPy array:
    return np.asarray(cy_array)

Это на самом деле прекрасно компилируется и работает без сбоев, но результат не совсем то, что я ожидал.Если я распечатываю значения массива NumPy, я получаю это:

000: [1, 2, 3, 4, 5, 6, 7, 8, 9]
001: [1, 0, 0, 0, 0, 0, 0, 113, 6]
002: [32, 32, 32, 32, 96, 96, 91, 91, 97]
[...]

, получается, что первая строка была отображена правильно, но другие строки выглядят скорее как неинициализированная память.Так что, вероятно, существует несоответствие с макетом памяти char** и режимом просмотра 2D по умолчанию в режиме по умолчанию.


Edit # 1 : What I 'Из моего другого вопроса я узнал, что встроенные массивы Cython не поддерживают непрямые макеты памяти, поэтому мне нужно создать оболочку Cython для unsigned char**, которая предоставляет буферный протокол

1 Ответ

0 голосов
/ 30 декабря 2018

Решение:

Вручную реализовать буферный протокол:

Класс-оболочка, который упаковывает unsigned char** и реализует буферный протокол (Indirect2DArray.pyx ):

cdef class Indirect2DArray:
    cdef Py_ssize_t len
    cdef unsigned char** raw_data
    cdef ndim
    cdef Py_ssize_t item_size
    cdef Py_ssize_t strides[2]
    cdef Py_ssize_t shape[2]
    cdef Py_ssize_t suboffsets[2]


    def __cinit__(self,int nrows,int ncols):
        self.ndim = 2
        self.len = nrows * ncols
        self.item_size = sizeof(unsigned char)

        self.shape[0] = nrows
        self.shape[1] = ncols

        self.strides[0] = sizeof(void*)
        self.strides[1] = sizeof(unsigned char)

        self.suboffsets[0] = 0
        self.suboffsets[1] = -1


    cdef set_raw_data(self, unsigned char** raw_data):
        self.raw_data = raw_data        

    def __getbuffer__(self,Py_buffer * buffer, int flags):
        if self.raw_data is NULL:
            raise Exception("raw_data was NULL when calling __getbuffer__ Use set_raw_data(...) before the buffer is requested!")

        buffer.buf = <void*> self.raw_data
        buffer.obj = self
        buffer.ndim = self.ndim
        buffer.len = self.len
        buffer.itemsize = self.item_size
        buffer.shape = self.shape
        buffer.strides = self.strides
        buffer.suboffsets = self.suboffsets
        buffer.format = "B" # unsigbed bytes


    def __releasebuffer__(self, Py_buffer * buffer):
        print("CALL TO __releasebuffer__")

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

Вот его использование:

def test_wrapper(self):
    cdef nrows= 10000
    cdef ncols = 81    

    cdef unsigned char** raw_pointer = self.raw_data
    wrapper = Indirect2DArray(nrows,ncols)    
    wrapper.set_raw_data(raw_pointer)

    # now create the memoryview:
    cdef unsigned char[::view.indirect_contiguous, ::1] view = wrapper

    # print some slices 
    print(list(view[0,0:30]))
    print(list(view[1,0:30]))
    print(list(view[2,0:30]))

, производящий следующий вывод:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9, 1, 2, 3, 7, 8, 9, 1, 2, 3, 4, 5, 6, 2, 1, 4]
[2, 1, 3, 4, 5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9, 1, 2, 3, 7, 8, 9, 1, 2, 3, 4, 5, 6, 1, 2, 4]
[3, 1, 2, 4, 5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9, 1, 2, 3, 7, 8, 9, 1, 2, 3, 4, 5, 6, 1, 2, 3]

Это именно то, что я ожидал.Спасибо всем, кто помог мне

...