2016-05-25 13 views
9

Cython documentation spiega molto bene cosa consentono, come è possibile dichiararli e come utilizzarli.Cython typed memoryviews: cosa sono veramente?

Tuttavia, non mi è ancora chiaro cosa siano realmente. Ad esempio, una semplice assegnazione da un array NumPy come questo:

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

può rendere l'Accesso alla/assegnazione di my_arr più veloce.

Cosa sta succedendo dietro le quinte? Numpy dovrebbe già allocare gli elementi in memoria in modo contiguo, quindi qual è l'accordo con memoryviews? Apparentemente non molto, infatti l'assegnazione memoryview dell'array NumPy new_arr dovrebbe essere equivalente a

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

in termini di velocità. Tuttavia, le visualizzazioni di memoria sono considerate più generali del buffer di array numpy; potresti fare un semplice esempio in cui la "generalizzazione" aggiunta è importante/interessante?

Inoltre, se ho già assegnato un puntatore per rendere le cose il più veloci possibile, qual è il vantaggio di lanciarlo su un memoryview tipizzato? (La risposta a questa domanda potrebbe essere la stessa di quella di cui sopra)

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

La prima riga della documentazione, 'Le schermate di memoria tipizzate consentono un accesso efficiente ai buffer di memoria, come quelli degli array NumPy sottostanti, senza incorrere in alcun sovraccarico di Python. "Mi sembra che' memoryview' sia un insieme proprio di 'cython 'di' c' funzioni per accedere a un buffer, ignorando le funzioni 'numpy'. Non sarà più veloce dell'accesso diretto a 'c', ma potrebbe essere più facile da usare. – hpaulj

risposta

14

Che cosa è un memoryview:

Quando si scrive in una funzione:

cdef double[:] a 

si finisce con un oggetto __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; 

Il memoryview contiene un puntatore C alcuni dati che (di solito) non possiedono direttamente. Contiene anche un puntatore a un oggetto Python sottostante (struct __pyx_memoryview_obj *memview;). Se i dati sono di proprietà di un oggetto Python, memview contiene un riferimento a questo e garantisce che l'oggetto Python che contiene i dati sia mantenuto attivo fino a quando la memoria è presente.

La combinazione del puntatore ai dati grezzi e le informazioni di come indicizzare esso (shape, strides e suboffsets) permette Cython fare indicizzare l'usando i puntatori ai dati grezzi e alcune semplici calcoli C (che è molto efficiente) . ad esempio:

x=a[0] 

dà qualcosa di simile:

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

Al contrario, se si lavora con gli oggetti senza tipo e scrivere qualcosa di simile:

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

l'indicizzazione è fatto come:

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

che si espande s a un gruppo di chiamate Python C-api (quindi è lento). In definitiva si chiama il metodo __getitem__.


Rispetto ad array NumPy tipizzati: non c'è davvero una differenza enorme. Se fate qualcosa di simile:

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

funziona praticamente molto simile a un memoryview, con accesso ai puntatori prime e la velocità dovrebbe essere molto simile.

Il vantaggio di utilizzare memoryviews è che è possibile utilizzare una gamma più ampia di tipi di array (ad esempio lo standard library array), in modo da essere più flessibili sui tipi con cui è possibile chiamare le funzioni. Questo si adatta all'idea generale di Python "digitando" - che il tuo codice dovrebbe funzionare con qualsiasi parametro che si comporta nel modo giusto (piuttosto che controllare il tipo).

Un secondo (piccolo) vantaggio è che non sono necessarie le intestazioni numpy per creare il modulo.

Un terzo (possibilmente più grande) vantaggio è che memoryviews può essere inizializzato senza GIL mentre cdef np.ndarray s no (http://docs.cython.org/src/userguide/memoryviews.html#comparison-to-the-old-buffer-support)

Un leggero svantaggio per memoryviews è che sembrano essere leggermente più lento da configurare.


Rispetto al solo utilizzando malloc ndr int puntatori:

non sarà possibile ottenere alcun vantaggio della velocità (ma nessuno ne avete perdita di velocità troppo). I vantaggi minori di conversione utilizzando un memoryview sono:

  1. è possibile scrivere funzioni che possono essere utilizzati sia da Python o internamente 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. si può lasciare Cython gestire la liberazione della memoria per questo tipo di array, che potrebbe semplificarti la vita per cose che hanno una durata sconosciuta. Vedere http://docs.cython.org/src/userguide/memoryviews.html#cython-arrays e in particolare .callback_free_data.

  3. È possibile passare i dati al codice python python (otterrà il sottostante __pyx_memoryview_obj o qualcosa di simile). Fai molta attenzione alla gestione della memoria qui (ad esempio vedi il punto 2!).

  4. L'altra cosa che puoi fare è gestire cose come array 2D definiti come puntatore a puntatore (ad esempio double**). Vedi http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts. Generalmente non mi piace questo tipo di array, ma se hai già un codice C che già usi se puoi puoi interfacciarlo (e passarlo a Python in modo che anche il tuo codice Python possa usarlo).

Problemi correlati