2 votes

Tableaux alloués à la même adresse Cython + Numpy

Je suis tombé sur un drôle de comportement de la mémoire en travaillant avec numpy + cython, en essayant de récupérer les données d'un tableau numpy comme un tableau C, à utiliser dans une fonction sans GIL. J'ai regardé à la fois l'API des tableaux de cython et de numpy mais je n'ai pas trouvé d'explication. Considérez donc les lignes de code suivantes :

cdef np.float32_t *a1 = <np.float32_t *>np.PyArray_DATA(np.empty(2, dtype="float32"))
print "{0:x}".format(<unsigned int>a1)
cdef np.float32_t *a2 = <np.float32_t *>np.PyArray_DATA(np.empty(2, dtype="float32"))
print "{0:x}".format(<unsigned int>a2)[]

J'alloue deux tableaux numpy avec la fonction empty de numpy, et je veux récupérer un pointeur vers le tampon de données pour chacun d'eux. On pourrait s'attendre à ce que ces deux pointeurs pointent vers deux adresses mémoire différentes sur le tas, éventuellement espacées de 2*4 octets. Mais non, j'obtiens des pointeurs vers la même adresse mémoire, par ex.

>>>96a7aec0
>>>96a7aec0

Comment cela se fait-il ? J'ai réussi à contourner ce problème en déclarant mes tableaux numpy en dehors de l'appel PyArray_DATA, dans ce cas, j'obtiens ce que j'attends.

La seule explication à laquelle je peux penser est que je ne crée aucun objet Python en dehors de la portée de la fonction PyArray_DATA, et que l'appel de cette fonction n'incrémente pas le nombre de références de Python. Par conséquent, le GC récupère cet espace mémoire juste après, et le tableau suivant est alloué à l'adresse mémoire précédente maintenant libre. Quelqu'un de plus calé en Python que moi pourrait-il confirmer cela ou donner une autre explication ?

2voto

oz1 Points 342

Vous créez deux tableaux numpy temporaires, ils se trouvent être à la même adresse. Comme aucune référence python n'est conservée pour eux, ils sont immédiatement ramassés, a1 y a2 deviennent également des pointeurs pendants.

Si des références sont conservées pour eux, leurs adresses ne peuvent pas être les mêmes, par exemple :

cdef int[:] a = np.arange(10)  # A memoryview will keep the numpy array from GC.
cdef int[:] b = np.arange(10)
cdef int* a_ptr = &a[0]
cdef int* b_ptr = &b[0]
print(<size_t>a_ptr)
print(<size_t>b_ptr)

Il faut être très prudent lorsqu'on utilise les données sous-jacentes d'un objet. En cas d'utilisation incorrecte, on rencontre souvent un pointeur qui pendouille. eg :

void cfunc(const char*)
# Fortunately, this won't compile in cython. 
# Error: Storing unsafe C derivative of temporary Python reference
cdef const char* = ("won't" + " compile").encode()
cfunc(char)

Le bon chemin :

# make sure keep_me is alive before cfunc have finished with it.
cdef bytes keep_me = ("right" + "way").encode() 
cfunc(temp)
# Or for single use.
cfunc(("right" + "way").encode())

Un autre exemple en c++ std::string membre de l'association c_str() :

// The result of `+` will immediately destructed. cfunc got a  dangling pointer.
const char * s = (string("not") + string("good")).c_str();
cfunc(s); 

Le bon chemin :

// keep `keep_me` for later use.
string keep_me = string("right") + string("way"); 
cfunc(keep_me.c_str());
// Or, for single use.
cfunc((string("right") + string("way")).c_str())

Référence : std::string::c_str() et les temporaires

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X