Supposons qu'il existe deux threads qui communiquent en s'envoyant des messages de données de manière asynchrone. Chaque thread a une sorte de file d'attente de messages.
Ma question est de très bas niveau: quel peut être le moyen le plus efficace de gérer la mémoire? Je peux penser à plusieurs solutions:
- L'expéditeur crée l'objet via
new
. Le récepteur appelledelete
. - Pool de mémoire (pour retransférer la mémoire à l'expéditeur)
- Collecte des ordures (par exemple, Boehm GC)
- (si les objets sont suffisamment petits) copier par valeur pour éviter l'allocation complète du tas
1) est la solution la plus évidente, je vais donc l'utiliser pour un prototype. Il y a de fortes chances qu'il soit déjà assez bon. Mais indépendamment de mon problème spécifique, je me demande quelle technique est la plus prometteuse si vous optimisez les performances.
Je m'attendrais à ce que la mise en commun soit théoriquement la meilleure, surtout parce que vous pouvez utiliser des connaissances supplémentaires sur le flux d'informations entre les threads. Cependant, je crains que ce soit aussi le plus difficile à réussir. Beaucoup de réglages ... :-(
La récupération de place devrait être assez facile à ajouter par la suite (après la solution 1), et je m'attendrais à ce qu'elle fonctionne très bien. Donc, je suppose que c'est la solution la plus pratique si 1) se révèle trop inefficace.
Si les objets sont petits et simples, la copie par valeur peut être la plus rapide. Cependant, je crains que cela n'impose des limitations inutiles à la mise en œuvre des messages pris en charge, donc je veux l'éviter.
la source
unique_ptr
, je suppose que vous voulez direshared_ptr
. Mais s'il ne fait aucun doute que l'utilisation d'un pointeur intelligent est bonne pour la gestion des ressources, cela ne change pas le fait que vous utilisez une forme d'allocation et de désallocation de mémoire. Je pense que cette question est plus bas niveau.La plus grande performance atteinte lors de la communication d'un objet d'un thread à un autre est la surcharge de saisie d'un verrou. C'est de l'ordre de plusieurs microsecondes, ce qui est nettement supérieur au temps moyen qu'une paire de
new
/delete
prend (de l'ordre d'une centaine de nanosecondes). Lesnew
implémentations sensées essaient d'éviter le verrouillage à presque tous les coûts pour éviter que leurs performances ne soient affectées.Cela dit, vous voulez vous assurer que vous n'avez pas besoin de saisir des verrous lors de la communication des objets d'un thread à un autre. Je connais deux méthodes générales pour y parvenir. Les deux ne fonctionnent que de manière unidirectionnelle entre un expéditeur et un récepteur:
Utilisez un tampon en anneau. Les deux processus contrôlent un pointeur dans ce tampon, l'un est le pointeur de lecture, l'autre est le pointeur d'écriture.
L'expéditeur vérifie d'abord s'il y a de la place pour ajouter un élément en comparant les pointeurs, puis ajoute l'élément, puis incrémente le pointeur d'écriture.
Le récepteur vérifie s'il y a un élément à lire en comparant les pointeurs, puis lit l'élément, puis incrémente le pointeur de lecture.
Les pointeurs doivent être atomiques car ils sont partagés entre les threads. Cependant, chaque pointeur n'est modifié que par un thread, l'autre n'a besoin que d'un accès en lecture au pointeur. Les éléments du tampon peuvent être des pointeurs eux-mêmes, ce qui vous permet de dimensionner facilement votre tampon en anneau à une taille qui ne fera pas le bloc expéditeur.
Utilisez une liste chaînée qui contient toujours au moins un élément. Le récepteur a un pointeur sur le premier élément, l'expéditeur a un pointeur sur le dernier élément. Ces pointeurs ne sont pas partagés.
L'expéditeur crée un nouveau nœud pour la liste liée, en définissant son
next
pointeur surnullptr
. Il met ensuite à jour lenext
pointeur du dernier élément pour pointer vers le nouvel élément. Enfin, il stocke le nouvel élément dans son propre pointeur.Le récepteur regarde le
next
pointeur du premier élément pour voir si de nouvelles données sont disponibles. Si tel est le cas, il supprime l'ancien premier élément, avance son propre pointeur pour pointer vers l'élément actuel et commence à le traiter.Dans cette configuration, les
next
pointeurs doivent être atomiques et l'expéditeur doit être sûr de ne pas déréférencer l'avant-dernier élément après avoir défini sonnext
pointeur. L'avantage est, bien sûr, que l'expéditeur n'a jamais à bloquer.Les deux approches sont beaucoup plus rapides que toute approche basée sur un verrou, mais elles nécessitent une mise en œuvre minutieuse pour bien fonctionner. Et, bien sûr, ils nécessitent une atomicité matérielle native des écritures / charges de pointeurs; si votre
atomic<>
implémentation utilise un verrou en interne, vous êtes à peu près condamné.De même, si vous avez plusieurs lecteurs et / ou écrivains, vous êtes à peu près condamné: vous pouvez essayer de proposer un schéma sans verrouillage, mais il sera difficile à mettre en œuvre au mieux. Ces situations sont beaucoup plus faciles à gérer avec une serrure. Cependant, une fois que vous avez saisi un verrou, vous pouvez cesser de vous soucier de
new
/delete
performance.la source