2 votes

Une alternative plus économique à std::atomic<bool> ?

J'ai une classe d'objets dans une application multithread où chaque thread peut marquer un objet pour le supprimer, puis un thread central de ramasse-miettes supprime effectivement l'objet. Les threads communiquent via des méthodes membres qui accèdent à un bool interne :

class MyObjects {
...   
bool shouldBeDeleted() const
{
   return m_Delete;
}

void
markForDelete()
{
   m_Delete = true;
}
...
   std::atomic< bool >                                        m_IsObsolete;
}

Le bool a été transformé en atomique par quelqu'un d'autre dans le passé parce que Thread Sanitizer ne cessait de se plaindre. Cependant, perf suggère maintenant qu'il y a une surcharge de traitement pendant le chargement atomique interne :

         cbz    x0, 3f4                                                                                                                                                                                                                                                                                                                                                                                            

        _ZNKSt13__atomic_baseIbE4loadESt12memory_order():                                                                                                                                                                                                                                                                                                                                                           

              {                                                                                                                                                                                                                                                                                                                                                                                                     

                memory_order __b = __m & __memory_order_mask;                                                                                                                                                                                                                                                                                                                                                       

                __glibcxx_assert(__b != memory_order_release);                                                                                                                                                                                                                                                                                                                                                      

                __glibcxx_assert(__b != memory_order_acq_rel);                                                                                                                                                                                                                                                                                                                                                      

                return __atomic_load_n(&_M_i, __m);                                                                                                                                                                                                                                                                                                                                                                 

          add    x0, x0, #0x40                                                                                                                                                                                                                                                                                                                                                                                          

 86,96        ldarb  w0, [x0]  

La plateforme cible est GCC, Aarch64 et Yocto Linux.

Maintenant, mes questions sont les suivantes :

  • L'atomique est-il vraiment nécessaire dans ce cas ? La transition du bool est à sens unique (de false à true) sans possibilité de retour en arrière tant que l'objet vit, donc une incohérence signifierait simplement que l'objet est supprimé un peu plus tard, non ?

  • Existe-t-il une alternative à std::atomic<bool> qui réduira au silence Thread Sanitizer mais qui est moins coûteux en calcul que std::atomic<bool> ?

3voto

JasonDiplomat Points 11

Une modification évidente pourrait être de spécifier memory_order_relaxed pour minimiser les barrières de mémoire.

Véase https://en.cppreference.com/w/cpp/atomic/memory_order

y https://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/

Voir également le classique "Atomic Weapons" de Herb Sutter : https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

m_Delete.store (true, std::memory_order_relaxed);

Caveat ( voir les articles ci-dessus ) - s'il y a des co-dépendances avec l'objet marqué pour la suppression (par exemple, une autre variable d'état, la libération de ressources, etc. memory_order_release pour s'assurer que le can be deleted l'activation du drapeau se produit dernier et n'est pas réordonné par l'optimiseur du compilateur.

En supposant que le "garbage collector" est seulement la vérification de la can be deleted seul, il n'aurait pas besoin d'utiliser memory_order_acquire dans la charge ; un relâchement serait suffisant. Sinon, il faudrait utiliser acquire pour garantir que tous les accès co-dépendants ne sont pas réordonnés pour se produire avant la lecture du drapeau.

2voto

davidbak Points 144

Le problème (tel que clarifié dans un commentaire de l'OP) n'est pas un véritable GC mais plutôt une suppression retardée d'objets sur un thread séparé afin de décharger les threads de traitement principaux du temps nécessaire à la suppression. Tous les objets à supprimer sont marqués comme tels à un moment donné - à un moment ultérieur, le thread de suppression arrive et les supprime.

Tout d'abord, est-il vraiment nécessaire de retarder la suppression afin d'atteindre les objectifs de performance du programme, en particulier la latence ? Il peut s'agir d'une surcharge supplémentaire qui a un impact réel sur la latence. (Ou peut-être y a-t-il également d'autres objectifs de performance, par exemple le débit, à prendre en compte). La suppression retardée n'est pas une évident La performance n'est pas gagnante dans tous les cas - vous devez déterminer si elle est appropriée dans les cas suivants chaque cas. (Par exemple, il se peut qu'il ne soit même pas nécessaire pour tous les suppressions : peut-être que certaines suppressions peuvent être effectuées immédiatement en ligne sans impact sur les performances, alors que d'autres doivent être différées. Cela pourrait être dû, par exemple, au fait que différents fils de traitement font des choses différentes avec des exigences de latence/de débit différentes).

Maintenant, une solution : Puisque nous parlons de suppression différée, il n'y a aucune raison pour que le thread de suppression ait besoin de scanner tous les objets à la recherche de ceux à supprimer (à chaque fois qu'il effectue un scan complet). Au lieu de cela, payez un coût légèrement plus élevé au moment où vous marquez l'objet pour la suppression et payez pas de coût pour scanner tous les objets. Pour ce faire, vous pouvez associer les objets supprimés à une liste de travail de suppression. Il y a un coût de synchronisation (qui peut être minimisé de diverses manières, en plus des verrous évidents), mais il est payé une fois par objet no une fois par objet et par scan .

(Il n'est pas nécessaire que ce soit une liste liée non plus. S'il y a une limite supérieure au nombre d'objets qui peuvent être supprimés dans une période de temps, vous pouvez simplement utiliser un tableau approprié).

D'autres possibilités sont ouvertes en caractérisant plus précisément ce problème comme une "suppression différée" plutôt qu'une "collecte de déchets" : certaines contraintes sont levées (d'autres sont peut-être ajoutées).

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