2012-01-03 14 views
11

Tengo una función C que mallocs() y rellena una matriz 2D de flotantes. "Devuelve" esa dirección y el tamaño de la matriz. La firma es¿Puedo forzar a un ndarray numpy a tomar posesión de su memoria?

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

Quiero llamarlo desde Python, entonces uso ctypes.

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

Nunca he averiguado cómo especificar tipos de argumentos con ctypes. Tiendo a escribir simplemente un contenedor de Python para cada función de C que estoy usando, y me aseguro de obtener los tipos en el contenedor. La matriz de flotadores es una matriz en orden de columna principal, y me gustaría obtenerla como numpy.ndarray. Pero es bastante grande, así que quiero usar la memoria asignada por la función C, no copiarla. (Acabo de encontrar estas cosas PyBuffer_FromMemory en esta respuesta 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) 

Esto me parece dar una matriz con los valores correctos. Pero estoy bastante seguro de que es una pérdida de memoria.

>>> a = get_array_py() 
>>> a.flags.owndata 
False 

La matriz no posee la memoria. Lo suficientemente justo; de forma predeterminada, cuando la matriz se crea desde un búfer, no debería. Pero en este caso debería. Cuando se elimina la matriz numpy, realmente me gustaría que Python libere la memoria buffer para mí. Parece que si pudiera forzar mis propios datos a True, eso debería hacerlo, pero mis propios datos no son configurables.

soluciones no son satisfactorios:

  1. hacer que la persona que llama de get_array_py() responsable de liberar la memoria. Eso es súper molesto; la persona que llama debería ser capaz de tratar esta matriz numpy como cualquier otra matriz numpy.

  2. Copie la matriz original en una nueva matriz numpy (con su propia memoria separada) en get_array_py, elimine la primera matriz y libere la memoria en get_array_py(). Devuelve la copia en lugar de la matriz original. Esto es molesto porque es una copia de memoria innecesaria.

¿Hay alguna manera de hacer lo que quiero? No puedo modificar la función C en sí, aunque podría agregar otra función C a la biblioteca si eso es útil.

+0

Esto suena como un mundo de dolor .. Creo que estás pidiendo [segfault hell] (http://xkcd.com/371/) – wim

+0

He intentado esto también sin éxito usando ctypes. Un módulo de extensión completo lo hace posible, pero son más trabajos para escribir. –

Respuesta

1

Me tienden a tener dos funciones exportadas de mi biblioteca 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 */ 

Entonces yo escribir mi envoltorio de Python [1] de get_array_c para asignar la matriz, a continuación, llamar get_array_c_nomalloc. Entonces Python tiene posee la memoria. Puede integrar este contenedor en su biblioteca para que su usuario nunca tenga que estar al tanto de la existencia de get_array_c_nomalloc.

[1] Esto ya no es un envoltorio, sino un adaptador.

+0

Lo siento, tuve la firma de get_array_c() mal! Se necesitan int_pointers_ para nrows y ncols: no sé cuán grande será la matriz, por lo que no puedo preasignar la matriz en python. –

+0

Bueno, alternativamente puedes hacer que tu contenedor python use un objeto para mantener la referencia/acceder a la memoria, y usar un finalizador para liberar la matriz ... No sé si eso viola tu estética o no, pero el usuario ganó ' t tiene que liberar explícitamente la memoria. – Matthew

6

Acabo de tropezar con esta pregunta, que sigue siendo un problema en agosto de 2013. Numpy es muy quisquilloso con la bandera OWNDATA: No hay forma de que pueda modificarse en el nivel de Python, por lo que es probable que los tipos no puedan para hacer esto.En el nivel C-API numpy - y ahora estamos hablando de una manera completamente diferente de hacer módulos de extensión de Python - uno tiene que establecer explícitamente la marca con:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA); 

en numpy < 1.7, uno tenía que ser aún más explícito:

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

Si uno tiene ningún control sobre la función de C/biblioteca subyacente, la mejor solución es pasar un array numpy vacío del tamaño apropiado de Python para almacenar el resultado en el principio básico es. esa asignación de memoria siempre debe hacerse en el nivel más alto posible, en este caso en el nivel del intérprete de Python.


Como Kynan comentado a continuación, si se utiliza Cython, usted tiene que exponer a la función PyArray_ENABLEFLAGS manualmente, vea este post Force NumPy ndarray to take ownership of its memory in Cython.

La documentación pertinente es here y here.

+0

¿Cómo conseguiría lo mismo en Cython? Desafortunadamente, 'PyArray_ENABLEFLAGS' no parece estar expuesto en' numpy.pxd'. – kynan

+1

Si la funcionalidad requerida no está expuesta a Cython, puede parchear Cython o editar el archivo C que genera manualmente. – Stefan

+0

Ninguno de esos me parece opciones muy sostenibles. Traté de extender lo que está expuesto por 'numpy.pxd' en mi archivo pyx [pero no tuve suerte con eso] (https://gist.github.com/kynan/ade36155b497c87e0bc5). – kynan

Cuestiones relacionadas