2012-01-03 6 views
11

Ho una funzione C che mallocs() e popola una matrice 2D di float. "Restituisce" quell'indirizzo e la dimensione dell'array. La firma èPosso forzare un numdy ndarray a diventare proprietario della sua memoria?

int get_array_c(float** addr, int* nrows, int* ncols); 

Voglio chiamarlo da Python, quindi uso i ctypes.

import ctypes 
mylib = ctypes.cdll.LoadLibrary('mylib.so') 
get_array_c = mylib.get_array_c 

Non ho mai capito come specificare i tipi di argomento con i tipi. Tendo a scrivere solo un wrapper python per ogni funzione C che sto usando, e mi assicuro di avere i tipi giusti nel wrapper. L'array di float è una matrice in ordine di colonna maggiore e mi piacerebbe ottenerla come numpy.ndarray. Ma è piuttosto grande, quindi voglio usare la memoria allocata dalla funzione C, non copiarla. (Ho appena trovato questa roba PyBuffer_FromMemory in questa risposta StackOverflow: https://stackoverflow.com/a/4355701/3691)

buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory 
buffer_from_memory.restype = ctypes.py_object 

import numpy 
def get_array_py(): 
    nrows = ctypes.c_int() 
    ncols = ctypes.c_int() 
    addr_ptr = ctypes.POINTER(ctypes.c_float)() 
    get_array_c(ctypes.byref(addr_ptr), ctypes.byref(nrows), ctypes.byref(ncols)) 
    buf = buffer_from_memory(addr_ptr, 4 * nrows * ncols) 
    return numpy.ndarray((nrows, ncols), dtype=numpy.float32, order='F', 
         buffer=buf) 

Questo sembra darmi un array con i valori giusti. Ma sono abbastanza sicuro che sia una perdita di memoria.

L'array non possiede la memoria. Giusto; per impostazione predefinita, quando la matrice viene creata da un buffer, non dovrebbe. Ma in questo caso dovrebbe. Quando l'array numpy viene cancellato, mi piacerebbe davvero che python liberasse la memoria buffer per me. Sembra che se potessi forzare owndata su True, dovrebbe farlo, ma owndata non è impostabile.

soluzioni insoddisfacenti:

  1. sfruttare al chiamante di get_array_py() responsabile di liberare la memoria. È super fastidioso; il chiamante dovrebbe essere in grado di trattare questo array numpy come qualsiasi altro array numpy.

  2. Copia l'array originale in un nuovo array numpy (con la sua memoria separata) in get_array_py, elimina il primo array e libera la memoria all'interno di get_array_py(). Restituisce la copia invece della matrice originale. Questo è fastidioso perché è una copia di memoria non necessaria.

C'è un modo per fare ciò che voglio? Non riesco a modificare la funzione C stessa, anche se potrei aggiungere un'altra funzione C alla libreria, se ciò è utile.

+0

Questo suona come un mondo di dolore .. Penso che tu stia chiedendo [segfault hell] (http://xkcd.com/371/) – wim

+0

Ho provato anche questo senza successo usando i ctype. Un modulo di estensione completo lo rende possibile ma è più un lavoro da scrivere. –

risposta

1

avrei tendono ad avere due funzioni esportate dalla mia libreria C:

int get_array_c_nomalloc(float* addr, int nrows, int ncols); /* Pass addr as argument */ 
int get_array_c(float **addr, int nrows, int ncols); /* Calls function above */ 

Vorrei quindi scrivere il mio wrapper Python [1] di get_array_c per allocare la matrice, quindi chiamare get_array_c_nomalloc. Quindi Python ha il proprietario della memoria. È possibile integrare questo wrapper nella libreria in modo che l'utente non debba mai essere a conoscenza dell'esistenza di get_array_c_nomalloc.

[1] Questo non è più un wrapper, ma un adattatore.

+0

Mi dispiace, ho sbagliato la firma per get_array_c()! Ci sono int _pointers_ per nrows e ncols - Non so quanto sarà grande l'array, quindi non posso preallocare l'array in python. –

+0

Beh, in alternativa puoi rendere il tuo wrapper python utilizzare un oggetto per contenere il riferimento/accedere alla memoria e usare un finalizzatore per liberare l'array ... Non so se questo violi l'estetica o no, ma l'utente ha vinto ' Devo liberare esplicitamente la memoria. – Matthew

6

Mi sono appena imbattuto in questa domanda, che è ancora un problema nell'agosto 2013. Numpy è davvero pignolo per il flag OWNDATA: Non c'è modo che possa essere modificato a livello di Python, quindi molto probabilmente i ctype non saranno in grado per fare questo.A livello C-API NumPy - e ora stiamo parlando di un modo completamente diverso di fare moduli di estensione Python - si deve impostare in modo esplicito la bandiera con:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA); 

Su NumPy < 1.7, uno doveva essere ancora più esplicito:

((PyArrayObject*)arr)->flags |= NPY_OWNDATA; 

Se si ha alcun controllo sul sottostante funzione C/library, la soluzione migliore è di passare un array NumPy vuoto della dimensione appropriata da Python per memorizzare il risultato nel il principio di base è. quella allocazione di memoria dovrebbe sempre essere eseguita al livello più alto possibile, in questo caso a livello dell'interprete Python.


Come Kynan commentato qui sotto, se si utilizza Cython, è necessario esporre la funzione PyArray_ENABLEFLAGS manualmente, vedere questo post Force NumPy ndarray to take ownership of its memory in Cython.

La documentazione pertinente è here e here.

+0

Come ottengo lo stesso risultato in Cython? Sfortunatamente 'PyArray_ENABLEFLAGS' sembra non essere esposto in' numpy.pxd'. – kynan

+1

Se la funzionalità richiesta non è esposta a Cython, è possibile correggere Cython o modificare il file C che genera manualmente. – Stefan

+0

Nessuna di queste opzioni mi sembra molto sostenibile. Ho provato ad estendere ciò che è esposto da 'numpy.pxd' nel mio file pyx [ma non ho avuto fortuna con quello] (https://gist.github.com/kynan/ade36155b497c87e0bc5). – kynan