2016-05-25 3 views
9

Cython documentation очень хорошо объясняет, что они позволяют, как вы можете объявить их и как их использовать.Cython набрал memoryviews: что они на самом деле?

Однако мне все еще не ясно, каковы они на самом деле. Например, простое задание из Numpy массива, как это:

my_arr = np.empty(10, np.int32) 
cdef int [:] new_arr = my_arr 

может сделать вступающее/назначение my_arr быстрее.

Что это происходит за кулисами? Numpy уже должен распределять элементы в памяти смежным образом, так что в чем дело с памятью? Видимо, не так много, на самом деле назначение memoryview из массива Numpy new_arr должно быть эквивалентно

cdef np.ndarray[np.int32_t, ndim=1] new_arr = np.empty(10, np.int32) 

с точки зрения скорости. Тем не менее, memoryviews считаются более общими, чем numpy array buffer; вы могли бы сделать простой пример, в котором добавленное «обобщение» важно/интересно?

Кроме того, если я уже выделил указатель, чтобы сделать все как можно быстрее, в чем преимущество приведения его в типизированное представление памяти? (Ответ на этот вопрос может быть такой же из приведенной выше)

cdef int *my_arr = <int *> malloc(N * sizeof(int)) 
cdef int[:] new_arr = <int[:N]>my_arr 
+1

Первая строка документации «Типизированные представления памяти позволяют эффективно использовать буферы памяти, такие как базовые массивы NumPy, без каких-либо накладных расходов Python. 'Мне кажется, что' memoryview' - это просто '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' 'для доступа к буферу, минуя функции 'numpy'. Это не будет быстрее, чем прямой доступ к стилю 'c', но может быть проще в использовании. – hpaulj

ответ

14

Что такое memoryview:

Когда вы пишете в функции:

cdef double[:] a 

вы в конечном итоге с объектом __Pyx_memviewslice:

typedef struct { 
    struct __pyx_memoryview_obj *memview; 
    char *data; 
    Py_ssize_t shape[8]; 
    Py_ssize_t strides[8]; 
    Py_ssize_t suboffsets[8]; 
} __Pyx_memviewslice; 

memoryview содержит C-указатель некоторых данных, которые он (обычно) не имеет непосредственно. Он также содержит указатель на базовый объект Python (struct __pyx_memoryview_obj *memview;). Если данные принадлежат объекту Python, то memview содержит ссылку на это и гарантирует, что объект Python, который хранит данные, сохраняется в живых, пока вокруг памяти.

Комбинация указателя на необработанные данные, а также информация о том, как индексироваться (shape, strides и suboffsets) позволяет Cython делать индексацию с использованием сырых указателей данных и некоторой простая математики C (который является очень эффективным) , например:

x=a[0] 

дает что-то вроде:

(*((double *) (/* dim=0 */ (__pyx_v_a.data + __pyx_t_2 * __pyx_v_a.strides[0])))); 

В противоположность этому, если вы работаете с нетипизированных объектов и написать что-то вроде:

a = np.array([1,2,3]) # note no typedef 
x = x[0] 

индексация осуществляется как:

__Pyx_GetItemInt(__pyx_v_a, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); 

который сам расширяется s на целую кучу вызовов C-api Python (так медленно). В конечном счете он вызывает метод a__getitem__.


По сравнению с типизированных Numpy массивов: там действительно не огромная разница. Если вы что-то вроде:

cdef np.ndarray[np.int32_t, ndim=1] new_arr 

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

Преимущество использования памяти в том, что с ними можно использовать более широкий диапазон типов массивов (например, standard library array), поэтому вы более гибко относитесь к типам, с которыми могут быть вызваны ваши функции. Это соответствует общей идее Python о «утиной печати» - что ваш код должен работать с любым параметром, который ведет себя правильно (вместо проверки типа).

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

Третье (возможно, больше) преимущества заключается в том, что memoryviews может быть инициализирован без GIL, пока cdef np.ndarray s не могу (http://docs.cython.org/src/userguide/memoryviews.html#comparison-to-the-old-buffer-support)

Незначительный недостаток memoryviews является то, что они, кажется, немного медленнее, чтобы настроить.


По сравнению с использованием только malloc ред Int указателей:

Вы не получите никакого преимущества в скорости (но ни вы получите слишком много потери скорости). Незначительные преимущества преобразования с использованием memoryview являются:

  1. Вы можете написать функции, которые могут быть использованы либо с Python или находящиеся внутри Cython:

    cpdef do_something_useful(double[:] x): 
        # can be called from Python with any array type or from Cython 
        # with something that's already a memoryview 
        .... 
    
  2. Вы можете позволить Cython обрабатывать освобождение памяти для этого типа массива, который может упростить вашу жизнь для вещей, которые имеют неизвестную жизнь. См. http://docs.cython.org/src/userguide/memoryviews.html#cython-arrays и особенно .callback_free_data.

  3. Вы можете передать свои данные обратно в код python python (он получит базовое поле __pyx_memoryview_obj или что-то подобное). Будьте очень осторожны в управлении памятью здесь (см. Пункт 2!).

  4. Другая вещь, которую вы можете сделать, это обработать такие вещи, как 2D-массивы, определенные как указатель на указатель (например, double**). См. http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts. Обычно мне не нравится этот тип массива, но если у вас уже есть C-код, который уже используется, то вы можете с ним взаимодействовать (и передать его обратно на Python, чтобы ваш код Python также мог его использовать).