[REDMINE1D-340] [RM-8427] [swing bindings] remove the std::vector -> numpy array wrapper Created: 20/Oct/23  Updated: 14/Nov/23  Resolved: 14/Nov/23

Status: Done
Project: 1D Redmine
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Normal
Reporter: Redmine-Jira Migtation Assignee: Redmine-Jira Migtation
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Created on 2023-10-19 14:15:44 by Didier Vibert. % Done: 100

The wrapper (pyconv.h) was written to be able to bind std::vector to numpy arrays. This wrapper has a drawback since it returns a raw pointer to the std::vector data buffer without having the property of this vector. Thus it is not controlling the life-time of the buffer which may not survive the use of the returned pointer that seems to be directly bind by swig to the numpy array buffer.

we can remove all these wrappers, since swig is able to bind a std::vector to a python list, and then we can convert the list to a numpy array in the python API code.

we should also check if swig 4 is able to bind a std::vector to a numpy array without any wrapper... If I am correct I think it can !

MR: https://gitlab.lam.fr/CPF/cpf-redshift/-/merge_requests/561



 Comments   
Comment by Redmine-Jira Migtation [ 14/Nov/23 ]

Comment by Didier Vibert on 2023-10-20 09:12:02:

la conversion simple implique 2 copies

en creusant un peu, la solution d'abandonner les wrapper et de passer par la simple conversion a l'inconvénient de génèrer 2 copies...

<pre>
std::vector -[swig std_vector.i]> python tuple --[python api]-> numpy array
</pre>

l'implémentation actuelle, aucune copie, MAIS...

<pre>
std::vector -[swig numpy.i (Argout View Arrays)]-> numpy array (même buffer que celui du std::vector)
</pre>
ne génère aucune copie si on passe l'adresse du buffer du std::vector mais pose alors le problème de durée de vie du buffer (cf "Argout View Arrays":https://numpy.org/doc/stable/reference/swig.interface-file.html#argout-view-arrays)

> There is almost no way to guarantee that the internal data from the C code will remain in existence for the entire lifetime of the NumPy array that encapsulates it. If the user destroys the object that provides the view of the data before destroying the NumPy array, then using that array may result in bad memory references or segmentation faults. Nevertheless, there are situations, working with large data sets, where you simply have no other choice.

Avec cette solution on peut à la place allouer dans le wrapper et copier le contenu, dans ce cas plus de risque de destruction intempestive du buffer, mais une fuite mémoire, car la desallocation du nouveau buffer ne sera jamais faite.

Alternative, avec une copie

Il y a une autre possibilité, qui génère une seule copie au lieu de deux, en modifiant le wrapper actuel, toujours via numpy.i mais en utilisant cette fois des "Memory Managed Argout View Arrays":https://numpy.org/doc/stable/reference/swig.interface-file.html#memory-managed-argout-view-arrays :
<pre>
std::vector -[swig numpy.i (Memory Managed Argout View Arrays)]-> numpy array (buffer alloué par le wrapper, désalloué par le code python à la disparition du numpy array)
</pre>

(cf https://scipy-cookbook.readthedocs.io/items/SWIG_Memory_Deallocation.html)

L'utilisation des "Memory Managed Argout View Arrays" permet de transférer à python le contenu d'un std::vector dont on ne connait pas la taille à l'avance, et dont pyhon va manager la desallocation (il faut alors allouer et copier le contenu du std::vector dans le wrapper c++)

Note: On peut aussi arriver au même résultat en utilisant des "Argout Arrays":https://numpy.org/doc/stable/reference/swig.interface-file.html#argout-arrays mais dans ce cas, lors de l'appel python pour retourner le tableau, il faut passer en argument la taille du tableau retourné (pour que swig alloue un numpy array de la bonne taille avant):
<pre><code class="python">
myarray = PC.Get_Float64Array(binded_vector, known_size)
</code></pre>
ce qui est moins "pythonic" mais faisable dans notre cas car nous voulons des getter sur des vecteurs déjà construits et dont on peut connaitre la taille en exportant une fonction qui la retourne.

Alternative sans copies

pas trouvé sans avoir de problèmes de memory management.

Comment by Redmine-Jira Migtation [ 14/Nov/23 ]

Comment by Ali Allaoui on 2023-10-20 09:14:23:
On ne fait pas ça un moment critique point de vue mémoire ou performance, donc de mon point de vue ce n'est pas grave de faire deux copies

Comment by Redmine-Jira Migtation [ 14/Nov/23 ]

Comment by Didier Vibert on 2023-10-20 09:26:16:
Ali Allaoui wrote in #note-2:
> On ne fait pas ça un moment critique point de vue mémoire ou performance, donc de mon point de vue ce n'est pas grave de faire deux copies

c'est pas faux, aujourd'hui.

De toute façon il faut modifier le code dans un sens ou dans l'autre, et ce n'est bien compliqué d'implémenter la version économe avec ce qu'on a déjà (pas de modif du code python, une allocation et copie à ajouter dans les fonctions de pyconv.h, et la déclaration modifiée pour passer des memory managed argout view arrays dans redshift.i)

Comment by Redmine-Jira Migtation [ 14/Nov/23 ]

Comment by Didier Vibert on 2023-10-27 13:12:59:
finalement j'ai implémenté la conversion vector -> numpy
qui gagne un peu en vitesse et en mémoire

Comment by Redmine-Jira Migtation [ 14/Nov/23 ]

Comment by Pierre-yves Chabaud on 2023-11-10 15:17:23:
Merged into @develop@ (@8e65b718@)

Generated at Sat Feb 10 15:31:44 JST 2024 using Jira 8.3.4#803005-sha1:1f96e09b3c60279a408a2ae47be3c745f571388b.