2D MemoryView из динамических массивов в Cython - PullRequest
1 голос
/ 30 октября 2019

Мне известен этот вопрос , но я искал более простой способ генерирования двухмерных представлений памяти из массивов Си. Поскольку я новичок в C и Cython, кто-нибудь может объяснить, почему что-то вроде

cdef int[:, :] get_zeros(int d):
    # get 2-row array of zeros with d as second dimension
    cdef int i
    cdef int *arr = <int *> malloc(sizeof(int) * d)
    for i in range(d):
        arr[i] = 0
    cdef int[:, :] arr_view
    arr_view[0, :] = <int[:d]>arr
    arr_view[1, :] = <int[:d]>arr
    return arr_view

не будет работать?

При компиляции я получаю Cannot assign type 'int[::1]' to 'int' как ошибку. Означает ли это, что 2d memview свернуто первым оператором присвоения 1d, или это потому, что просмотрам памяти нужны смежные блоки и т. Д.?

Ответы [ 2 ]

2 голосов
/ 31 октября 2019

Очевидно, что довольно сложно «объяснить, почему что-то [...] не сработает», потому что в конечном итоге это просто проектное решение, которое могло быть принято по-другому. Но:

Представления памяти Cython сделаны довольно глупыми. Все, что они делают, - это предоставляют какой-то приятный синтаксис для доступа к памяти чего-то, что реализует буферный протокол Python , а затем имеют чуть-чуть дополнительного синтаксиса, чтобы вы могли делать такие вещи, как получение 1D-памяти в виде указателя.

Кроме того, просмотр памяти в целом что-то оборачивает. Когда вы создаете cdef int[:, :] arr_view, оно недействительно, пока вы не сделаете arr_view = something. Попытки присвоить его части бессмысленны, поскольку (а) он делегировал бы назначение объекту, который оборачивает, используя буферный протокол, и (б) как именно будет работать назначение, будет зависеть от того, какой формат протокола буфера вы оборачиваете,То, что вы сделали , может быть верным, если обернуть «косвенный» объект протокола буфера, но не имеет смысла, если обернуть непрерывный массив. Поскольку arr_view может быть упаковкой, либо компилятор Cython должен рассматривать это как ошибку.

Вопрос, который вы связываете с , реализует буферный протокол и поэтому является правильным способом реализации такого рода. массива. То, что вы пытаетесь сделать, - это взять дополнительный синтаксис, который дает одномерное представление памяти из указателя, и принудительно включить его в часть двухмерного обзора памяти в смутной надежде, что это может сработать. Для этого требуется много логики, выходящей далеко за рамки того, для чего предназначено представление памяти Cython.


Вероятно, стоит сделать пару дополнительных замечаний:

  • Просмотр указателей в памяти не справляется с освобождением указателей (поскольку для них было бы практически невозможно угадать, что вы хотите). Вы должны справиться с этой логикой. Ваш текущий дизайн будет утечка памяти, если он работает. В проекте, который вы связали с классом упаковки, это может быть реализовано в __dealloc__ (хотя в этом ответе это не показано) и, следовательно, намного лучше.

  • Мое личное мнение таково "рваные массивы "(2D-массивы указателей на указатели) ужасны. Они требуют много распределения и освобождения. Есть много возможностей их наполовину инициализировать. Доступ к ним требует нескольких уровней косвенности и поэтому медленный. Единственное, что им нужно, это то, что они обеспечивают синтаксис arr[idx1][idx2] на C. В общем, я очень предпочитаю подход Numpy для выделения одномерного массива и использования shape / step для определения, куда индексировать. (Очевидно, что если вы оборачиваете существующую библиотеку, то, возможно, вы не сможете выбрать ее ...)

1 голос
/ 01 ноября 2019

В дополнение к замечательному ответу @DavidW я хотел бы добавить еще немного информации. В вашем включенном коде я вижу, что вы размещаете массив целых чисел, а затем обнуляете содержимое в цикле for. Более удобный способ сделать это - использовать вместо этого функцию calloc C, которая гарантирует указатель на обнуленную память и впоследствии не требует цикла for.

Кроме того, вы можете создать один тип int *, который указывает на«массив» данных, который имеет общий размер 2 * d * sizeof (int). Это будет гарантировать, что обе «строки» данных будут смежными друг с другом, а не раздельными и рваными. Затем это может быть приведено непосредственно к 2-мерному виду памяти.

Как и было обещано в комментариях, вот как может выглядеть этот код преобразования (с включенным использованием calloc):

cdef int[:, :] get_zeros(int d):    
    cdef int *arr = <int *>calloc(2 * d, sizeof(int))
    cdef int[:, :] arr_view = <int[:2, :d]>arr
    return arr_view

Там такжеПохоже, эквивалент calloc в c-api Python для документов, если вы хотите попробовать его. Тем не менее, похоже, что он не заключен в в модуле mem.pxd в Cython, поэтому вы, вероятно, не смогли его найти. Вы можете объявить подобный блок extern в своем коде, чтобы обернуть его, как другие функции, включенные в эту ссылку.

А вот бонусная ссылка , если вы хотите узнать больше о написаниираспределитель для распределения памяти из большого блока, если вы идете по пути предварительного выделения (то есть то, что функции PyMem_ * могут делать за кулисами, но более настраиваемые и под вашим контролем для вашего конкретного случая использования).

...