Journal Tests de bibliothèques signal-slot en C++

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
40
28
avr.
2020

Sommaire

Le savais-tu, chaque jour de nouvelles bibliothèques C++ pour gérer des signaux et des slots voient le jour. Il y en a tellement qu'on estime aujourd'hui qu'il existe environ 1,14 bibliothèques de ce type pour chaque développeur C++. Jetons-y un coup d'œil.

Le guépard court plus vite qu'une armoire

Le mécanisme dit de signal et de slot est une façon d'implémenter le patron de conception de l'observateur. Dans l'idée, le principe consiste à permettre l'inscription à un événement sous la forme de fonctions de rappel qui seront donc appelées quand ledit événement se produit. Le signal (ou observable) est celui qui émet l'événement et les slots (ou observateurs) sont les fonctions de rappel. Nous appellerons « connexion » l'entité représentant l'enregistrement d'un slot dans un signal.

Il existe un dépôt signal-slot-benchmarks sur GitHub regroupant une trentaine de bibliothèques au sein d'un benchmark global permettant d'avoir un point de comparaison des performances des unes et des autres. C'est un bon point de départ. J'ai découvert ce dépôt lors de l'annonce d'une nouvelle bibliothèque sur le subreddit concernant le C++. Évidemment en tant qu'auteur d'une bibliothèque similaire j'ai voulu m'insérer dans le benchmark.

Et je ne suis pas très bien classé.

Suite à ça j'ai fait un tas de mesures, revu mon code un paquet de fois, et malgré cela j'ai du mal à encore améliorer les performances. Mais que font les autres que je ne fais pas ? Où plutôt, que ne font-ils pas que je fais ?

Il s'avère que dans mon implémentation les slots sont copiés avant le déclenchement afin d'éviter de gérer des histoires de réentrance et de modification de la liste des slots pendant l'activation. En particulier, je souhaite qu'une fonction de rappel inscrite pendant le déclenchement ne soit pas appelé pendant l'itération en cours. Néanmoins une fonction désinscrite pendant le déclenchement ne doit pas être appelée. Cette copie coûte très cher.

D'autres implémentations ne s'embêtent pas avec cela et n'acceptent tout simplement pas que le signal soit modifié pendant qu'il est déclenché, quitte à planter le programme. Difficile dans ces conditions de connecter un slot à usage unique, par exemple, qui se désinscrit lui-même quand il est appelé.

Cette différence d'implémentation m'a amené à regarder les implémentations plus en détail puis, en m'appuyant sur le dépôt de benchmark, à comparer les bibliothèques non plus seulement par rapport à leurs performances mais aussi par rapport à leurs fonctionnalités. Après tout, si on doit comparer les performances de deux choses il faut le faire pour le même service rendu ; autrement on compare des pommes avec des oranges.

En codant un peu de glu pour uniformiser l'interface des différentes bibliothèques incluses dans signal-slot-benchmarks j'ai pu les faire passer dans les tests déjà implémentés dans iscool::signals. Alors évidemment, vu l'origine de ces tests tu te doutes bien qu'il y a un biais en faveur de mon implémentation. Il s'avère aussi que je voulais avoir le même comportement que Boost.Signals2 sur de nombreux aspects, c'est pourquoi ces deux bibliothèques passent tous les tests (quasiment, j'y reviendrai).

Néanmoins, même si d'autres bibliothèques ne passent pas quelques tests pour des questions de divergence dans les choix d'implémentations, certaines restent de très bons outils. Voyons ça plus en détail.

Liste des bibliothèques participant au comparatif

Ci-dessous les bibliothèques testées dans le benchmark, le tag qui les représentera par la suite (souvent un trigramme) et la date de leur dernière mise à jour à l'heure où j'écris ces lignes (avril 2020).

Vous êtes tous différents

Il y a un peu de tout là dedans. Déjà on remarque que certaines bibliothèques ne sont plus maintenues depuis longtemps. Ensuite si on regarde dans les détails il y a quelques subtilités, par exemple :

cps, css, jls et d'autres, requièrent que chaque fonction de rappel attachée à un signal soit une méthode d'une instance dérivant d'une classe donnée. Pour prendre des termes objets, imaginez par exemple qu'il faille dériver d'Observer pour s'enregistrer auprès d'un Observable. Impossible dans ce cas de connecter une fonction libre ou une lambda.

aco, jos, nls, nss, psg, wnk, yas et peut-être d'autres requièrent d'avoir une référence vers le signal d'origine pour couper une connexion. D'autres implémentations permettent de garder une connexion au delà de la durée de vie du signal d'origine (elles sont automatiquement coupées à la destruction du signal) et d'affecter de nouvelles connexions à des instances existantes.

aco, css, jos, mws, nls, psg, pss, sss et peut-être d'autres imposent que les fonctions de rappel ne retournent pas de valeur. D'autres implémentations autorisent les fonctions à retourner une valeur et proposent même différentes façons d'agréger les résultats.

bs2, ics, jls, lfs, lss, mws, nes, nod, nss, psg, vdk et wnk permettent de savoir si un signal a au moins un slot. Les autres ne le permettent pas.

Seuls asg, bs2, dob, evl, ics, jls, ksc, lfs,mws,nod,pssetvdk` proposent un moyen de tester si une connexion est active ou pas.

aco, bs2, cls, ics, jls, ksc, lfs, mws, nod, nss, psg, pss, vdk et yas fournissent une méthode pour déconnecter tous les slots d'un coup. asg, cps, cps, dob, evl, jos, nes, nls, nls, sss et wnk n'ont pas de telle méthode pour le faire mais n'empêchent pas d'affecter un signal vide à une instance existante (s = signal()), a priori pour un résultat similaire. css et lss ne permettent pas de déconnecter tous les slots d'un coup, ni via une méthode ni via l'affectation.

css, lss, mws, nod, nss et vdk n'autorisent pas l'échange (le swap) de deux instance de signaux.

cls, dob, ksc, lfs et vdk permettent l'accès à un signal depuis plusieurs threads. aco, asg, evl, ics, jls, jos, lss, mws, nes, psg, sss, wnk et yas sont uniquement mono-thread. bs2, cps, nls, nod, nss, pss proposent les deux configurations.

Et puis il y a d'autres petites singularités comme avoir un type de connexion différent selon le signal (jos), avoir autant de types de signaux que d'arités de slots (signal0, signal1… dans psg), utiliser un type privé de la classe de signal en retour de la fonction d'enregistrement des slots (nls, difficile de stocker la connexion dans ce cas) et d'autres trucs… disons inattendus.

Le point de vue de l'auteur

Lorsque j'ai implémenté iscool::signals j'avais trois objectifs en tête, issus de l'expérience acquise sur les jeux que je développais :

  • mono-thread : je n'ai quasiment jamais eu besoin de partager un signal entre plusieurs threads. Le mono-thread est le cas général, le multi-thread le cas particulier. Je préfère faire un effort pour gérer la synchronisation dans le peu de cas particuliers plutôt que de la payer pour rien dans le cas général.
  • à la Boost.Signals2 : nous avions utilisé Boost.Signals2 en première implémentation dans nos jeux. Connue, éprouvée et solide, cette bibliothèque était le meilleur choix pour commencer. Lors de la migration il fallait que la nouvelle bibliothèque ait un fonctionnement et une interface compatibles pour que cela se fasse en douceur.
  • temps de compilation : un des problèmes avec Boost était que son utilisation contribuait fortement à l'augmentation des temps de compilation tant pour interpréter les nombreux templates dans les fichiers d'entêtes que pour résoudre les symboles lors de l'édition des liens. La nouvelle implémentation devait en mettre le moins possible dans les entêtes et prévenir la duplication des symboles (bonjour extern template).

Par conséquent de nombreuses bibliothèques parmi celles listés précédemment sont à mon avis clairement inadaptées. Si votre bibliothèque est intrusive, ou qu'elle ne permet pas de créer un slot pour une lambda, ou que son implémentation tient dans un paquet de fichiers d'entêtes, alors je ne pourrai pas l'utiliser.

Mais que fais-tu?

Retour dans les tests. Pour comparer les bibliothèques j'ai repris les tests d'iscool::signals et j'en ai ajouté quelques uns. Les résultats sont listés dans le dépôt de signal-slot-benchmarks.

Les tests sont regroupés en quatre catégories :

  1. activation : que se passe-t-il quand un signal est déclenché ;
  2. paramètre : comment le signal transmet-il ses paramètres aux fonctions de rappel ;
  3. gestion des connexions : faut-il conserver les connexions et est-ce que la déconnexion empêche bien l'appel ;
  4. échange : que se passe-t-il si j'échange deux signaux.

Avant de regarder les résultats, quelques remarques sur leur interprétation. Le cas simple est évidemment celui où le test passe ; dans ce cas, rien à redire. Quand le test ne passe pas il se peut que cela soit simplement une divergence de conception entre iscool::signals et la bibliothèque testée, auquel cas on ne peut pas considérer que la bibliothèque soit défaillante. Par exemple, iscool::signals garantit que les fonctions soient appelées dans l'ordre dans lequel elles ont été inscrites. C'est juste un choix d'implémentation (sur lequel nous comptions dans nos jeux) mais pas une qualité intrinsèque d'un système de signaux et de slots.

Dans le cas où la bibliothèque testée ne fournit pas les méthodes nécessaires pour le test, le résultat est tout simplement ignoré. Après tout, si la fonction n'est pas disponible elle n'est pas erronée pour autant.

Enfin, il y a des bibliothèques qui font carrément planter certains tests. La ça devient réellement problématique d'autant plus que les cas testés me sembles légitimes. Par exemple la déconnexion d'un slot pendant son exécution est une situation que je rencontre fréquemment en pratique et malheureusement certaines bibliothèques ne le supportent pas.

Activation de signaux

Commençons donc par le déclenchement des signaux. Le premier test est le plus évident : est-ce que l'activation d'un signal déclenche l'appel d'une fonction qui lui est connectée ? Toutes les bibliothèques valident ce test.

Deuxième test, l'ordre d'appel correspond-il à celui dans lequel les fonctions ont été connectées ? Seuls cls, cps et nss_ts ne valident pas ce test. Comme expliqué auparavant, il s'agit plus d'un choix de conception plutôt qu'un problème.

Troisième test, si je connecte une fonction pendant l'exécution d'un signal, est-ce qu'elle ne sera pas appelée durant l'activation en cours ? asg, css, jls, lss, mws, nss_tss, sss et vdk ne valident pas ce test. De plus, aco, cls, cps, evl, nes, nss_ts, pss_st, wnk et yas crashent complètement pendant ce test. Là encore la décision d'exécuter ou non la fonction pendant l'activation du signal peut correspondre à un choix de conception. Personnellement je considère qu'une fonction qui n'était pas enregistrée lors de l'émission de l'événement ne devrait pas en être notifiée, mais après tout, ça se discute. Dans tout les cas, il n'y a pas de raison de crasher dans une telle situation.

Quatrième test, si je coupe une connexion pendant l'exécution d'un signal, est-ce que la fonction correspondante ne sera pas appelée dans l'itération en cours ? aco, cps_st, nod, nod_st, nss_st, nss_sts, nss_tss et psg ne passent pas ce test. cls, cps, nss_ts et yas crashent le test. Pour le coup j'ai du mal à imaginer une situation où il est acceptable qu'une fonction soit appelée alors qu'elle a été déconnectée.

Cinquième test, puis-je déclencher un signal pendant qu'il est déclenché ? En d'autres termes, est-ce qu'un signal peut être déclenché récursivement ? Toutes les implémentations passent ce test sauf cls, cps et nss_ts qui crashent.

Enfin, sixième et dernier test, est-ce qu'une fonction enregistrée deux fois dans au même signal est bien appelée deux fois lorsqu'il est déclenché ? La façon dont j'ai implémenté les interfaces vers les bibliothèques ne permet pas d'enregistrer la même fonction deux fois pour celles qui requièrent un héritage pour les slots, par conséquent aco, cps, cps_st, css, jls, nes, nss_st, nss_sts, nss_ts, nss_tss, psg, sss, vdk, wnk et yas ne sont pas testées. Toutes les autres bibliothèques passent le test.

Au final, seuls bs2, bs2_st, dob, ics, ksc, nls, nls_st et pss valident tous les tests de cette catégorie.

Activation avec un paramètre

Le premier test de cette catégorie vérifie que le signal accepte un paramètre et le transmet bien jusqu'au slot. Toutes les implémentations valident ce test.

Le deuxième test s'assure qu'aucune copie du paramètre n'est faite lorsque le slot et le signal prennent le paramètre par adresse. Là encore, toutes les bibliothèques passent le test.

Enfin, le troisième test vérifie que le nombre de copies du paramètre est minimal quand le signal et le slot prennent le paramètre par valeur. En pratique je n'ai jamais trouvé d'implémentation de fonction wrapper qui ne fasse pas au moins une copie quand les paramètres sont par valeur, par conséquent j'ai mis le seuil à une copie maximum pour ce test. Seul lfs valide ce test.

Au final, seul lfs valide tous les tests de cette catégorie.

Gestion des connexions

Là encore on commence par du simplissime, est-ce qu'un signal sans connexion indique bien qu'il n'a pas de connexion ? Évidemment toutes les bibliothèques qui fournissent un moyen de tester si un signal a une connexion ou pas passent ce test, soit bs2, bs2_st, ics, jls, lfs, lss, mws, nes, nod, nod_st, nss_st, nss_sts, nss_ts, nss_tss, psg, vdk, et wnk.

Le deuxième test est son symétrique, est-ce qu'un signal ayant une connexion indique bien qu'il a une connexion ? Les même implémentations valident ce test.

Troisième test, est-ce que la fonction connectée au signal sera appelée au déclenchement de ce dernier si je ne stocke pas l'objet représentant la connexion ? aco, cls, cps, cps_st, css, evl, jls, nss_st, nss_sts, nss_ts, nss_tss et sss ne valident pas ce test. Personnellement je n'ai pas d'avis sur le fait d'imposer ou non de stocker les connexions. D'un côté le fait de les stocker force le programmeur à faire attention à la durée de vie de ses fonctions de rappel, d'un autre côté permettre d'ignorer la connexion réduit le bruit quand il est certain que le signal sera détruit avant les dépendances des fonctions connectées.

Quatrième test, est-ce que la fonction enregistrée ne sera pas appelée si le signal est déclenché après que la connexion soit coupée ? Toutes les bibliothèques valident ce test.

Cinquième et dernier test, est-ce que la fonction ne sera pas appelée si le signal est remis à zéro ? Pour rappel certaines implémentations ne fournissent pas de méthode pour remettre le signal à zéro, et pour certaines d'entre elles il est possible d'affecter à une instance existante un signal fraîchement créé. Pour ce test, css et lss ne permettent aucunement de remettre le signal à zéro. cps, cps_st, dob et mws ne valident pas le test, tandis que sss crashe tout simplement. Les autres bibliothèques valident le test.

Au final, seuls bs2, bs2_st, ics, lfs, nes, nod, nod_st, psg, vdk et wnk valident tous les tests de cette catégorie.

Échange

L'échange de signaux est une fonctionnalité que j'utilise pour avoir des signaux qui déconnectent automatiquement les fonctions de rappel lors du déclenchement. Dans ce cas, lorsque je déclenche le signal je procède en 3 étapes :

  1. création d'un signal temporaire,
  2. échange du signal temporaire avec l'instance active,
  3. déclenchement de l'instance temporaire.

Ainsi à l'issue de la troisième étape toutes les fonctions de rappel sont déconnectées. Éventuellement de nouvelles connexions peuvent être faites pendant le déclenchement, auquel cas elles seront activées à l'itération suivante.

Plusieurs bibliothèques ne permettent aucunement d'échanger deux instances de signaux. css, lss, mws, nod, nod_st, nss_st, nss_sts, nss_ts, nss_tss et vdk sont dans ce cas. Par conséquent elles ne valident pas les tests ci-dessous.

Le premier test effectue un échange de deux signaux vides puis vérifie qu'ils sont vides. Toutes les implémentations passent ce test sauf cls qui crashe.

Le deuxième et le troisième test échangent un signal vide avec un signal ayant respectivement une et deux connexions, puis il les déclenche. cps, cps_st, dob, nls et nls_st ne valident pas ce test. cls et sss plantent.

Le quatrième test échange un signal ayant une connexion avec un autre signal ayant une connexion, puis il les déclenche. cls, cps, cps_st, dob, nls et nls_st ne passent pas ce test. jls et sss crashent.

Le cinquième test échange un signal ayant une connexion avec un signal ayant deux connexions et le sixième test échange un signal ayant deux connexions avec un signal ayant deux connexions. Puis les signaux sont déclenchés. Les résultats sont les mêmes que pour le quatrième test.

Le septième test échange un signal avec un autre pendant le déclenchement du premier. Seuls bs2, bs2_st, evl, ics, ksc, lfs, pss, pss_st et yas valident ce test. cls, cps, jos, psg, sss et wnk crashent dans ce cas.

Enfin le dernier test vérifie que les connexions de signaux échangés sont bien associées à l'instance ayant reçu le signal qui les a créées. cls, cps, cps_st, dob, evl, lfs, nls et nls_st ne passent pas ce test. jls et sss crashent.

Au final, seuls bs2, bs2_st, ics, ksc, pss, pss_st et yas valident tous les tests de cette catégorie.

On fait le bilan

Lorsque j'ai commencé à migrer les tests d'iscool::signals pour les appliquer aux autres bibliothèques je me doutais que le résultat ressemblerait plus à une validation de conformité à Boost.Signals2 plutôt qu'à un ensemble de caractéristiques attendues de toute bibliothèque de ce genre. Néanmoins je suis surpris de la disparités des résultats.

Certaines propriétés me semblent facultatives, comme l'ordre d'appel des fonctions par le signal par exemple, mais d'autres me semblent essentielles, comme l'ajout et la suppression de connexions à un signal pendant le déclenchement de celui-ci. Je ne compte plus le nombre de cas dans nos jeux où la première chose que fait une fonction de rappel est de couper la connexion au signal qui l'a appelée. De même pour l'échange de signaux, qui est une fonctionnalité que nous avons utilisé à plusieurs reprises.

Enfin il y a les crashs. Après tout, que le signal ne supporte pas l'échange ou une autre fonctionnalité, pourquoi pas, mais dans ce cas il faut que l'implémentation ne permette pas de le faire. Et là je crois que c'est une des difficultés du C++, il suffit d'un peu d'inexpérience ou d'une légère inattention et on se retrouve avec des cas qui font planter le programme. Bravo aux auteurs qui ont pris soin d'interdire l'échange quand leur implémentation ne le permettait pas.

Pour en revenir aux performances d'iscool::signals par rapport aux autres, puisque c'était le problème à l'origine de tout cela, il s'avère finalement qu'à fonctionnalités équivalentes c'est l'implémentation la plus efficace. Hourra ! D'ailleurs jusqu'à ce que j'ajoute le test sur le nombre de copies des paramètres par valeur, c'était la seule implémentation qui validait tous les tests, avec celles de Boost.Signals2. Quelle idée d'ajouter ce test alors que j'arrivais au bout de cet article… Allez bon, ça me fera un autre élément à améliorer.

Pour finir, si toi aussi tu as codé un système signal-slot, je t'encourage à l'intégrer au benchmark et aux tests. C'est à la fois enrichissant et très instructif, et ça te permettra de te situer par rapport à l'existant.

  • # Remarques

    Posté par  . Évalué à 7.

    Il existe un dépôt signal-slot-benchmarks sur GitHub regroupant une trentaine de bibliothèques au sein d'un benchmark global permettant d'avoir un point de comparaison des performances des unes et des autres. C'est un bon point de départ.

    Un benchmark est rarement un bon point d'entrée je trouve et le reste de ton journal montre en partie pourquoi. Pour moi ce qui est important c'est moins de connaitre la performance (déjà qu'est-ce que l'on met derrière la performance ?), mais la philosophie derrière.

    Ta bibliothèque est bidirectionnelle orientée jeu avec 2 aspects importants : possibilité de modifier dynamiquement et à haute fréquence les connexions signal/slot et le mono-threading pour ne pas payer le coût du thread-safe par défaut.

    Ce genre de petites descriptions sont pour moi bien plus intéressantes car elles donnent une bonne idée de à quoi s'attendre en terme de fonctionnalité et en terme de performance. Ça limite aussi les usages à contre-emploi.

    Par exemple, iscool::signals garantit que les fonctions soient appelées dans l'ordre dans lequel elles ont été inscrites. C'est juste un choix d'implémentation (sur lequel nous comptions dans nos jeux) mais pas une qualité intrinsèque d'un système de signaux et de slots.

    Tu as regardé si c'était l'ordre inverse (comme une pile) ou stable ? Et surtout il me semble qu'il y a un corollaire à ça : l'ordre des exécutions contraint la réduction des données en retour. S'il n'est pas prédictible la réduction que tu applique aux retours de tes signaux doit être commutatif.

    bs2, ics, jls, lfs, lss, mws, nes, nod, nss, psg, vdk et wnk permettent de savoir si un signal a au moins un slot. Les autres ne le permettent pas.

    Je me doute que ça répond à un besoin, mais je vois ça comme un anti-pattern. Pour moi le signal-slot est là pour avoir un découplage entre le signal et le slot. Sinon autant utiliser des callbacks. Mais je dirais aussi la même chose pour le retour des signaux.

    J'aurais pensé que les signal/slot est un pattern unidirectionnel qui permet à un « bout de code » de signaler 0, 1 ou plus d'autres fonctions. Il par rapport à l'appel direct de méthode, il permet un dispatch dynamique et par rapport aux callback, il permet principalement une simplification (le slot n'a pas à gérer ses clients) ce qui apporte un meilleur découplage.

    Mais c'est ma vision probablement biaisée et ma lecture du pattern observable.

    • [^] # Re: Remarques

      Posté par  (site Web personnel) . Évalué à -4.

      Un benchmark est rarement un bon point d'entrée je trouve

      Suivi de :

      2 aspects importants […] et le mono-threading pour ne pas payer le coût du thread-safe par défaut.

      Pas cohérent! Tu dis que la perf n'est pas important pour ensuite dire que 1 des 2 points importants est la perf.
      En effet, perso je préfère une lib multi-thread à une lib mono-thread si la lib multi-thread est plus rapide… La, tu viens de dire que c'est plus important pour toi d'avoir une lib mono-thread (ta priorité) même si elle est plus lente que lib multi-thread (tu ne veux pas savoir si c'est plus rapide).

      le mono-threading est ici un moyen (potentiel, absolument pas une garantie) d'être plus performant (qu'il faut donc tester, donc le benchmark est important), pas un aspect important (ça serait plutôt une limitation en vue d'être plus performant).

      Il te faut donc décider si la perf est importante (et la tu as besoin d'un benchmark) ou pas (et la tu t'en fous que la lib soit mono-thread ou pas).

      • [^] # Re: Remarques

        Posté par  . Évalué à 7.

        En quoi dire qu'un benchmark n'est pas un bon point d'entré signifie-t-il que la performance n'est pas importante ?

        Moi je comprends le commentaire comme avant de s'intéresser à comparer les performances dans le cadre d'un benchmark, il est intéressant de connaître les philosophies derrières (qui indiquent déjà beaucoup en termes de performance possible).

        Surtout, ne pas tout prendre au sérieux !

      • [^] # Re: Remarques

        Posté par  . Évalué à 3.

        Pas cohérent! Tu dis que la perf n'est pas important pour ensuite dire que 1 des 2 points importants est la perf.

        Benchmark et performance sont 2 choses très différentes. Un benchmark est une mesure de performance dans un contexte donné. Ce contexte est hyper important, si tu t'intéresse à la performance. Et c'est potentiellement compliqué de connaître le contexte dans le quel a était conçu un benchmark. Mais surtout je ne dis pas qu'il faut connaître la notion de perf de toutes les bibliothèques, une bibliothèque peut ne pas parler de performance dans sa description, si c'est ce qui t'intéresse tu sais que ce n'est pas la priorité du projet, si leur objectif c'est d'avoir une certaine forme d'API ou un maximum de sécurité ou je ne sais quoi c'est ce qui devrait ressortir d'une description.

        Bref non je ne vois pas où est l'incohérence.

      • [^] # Re: Remarques

        Posté par  (site Web personnel) . Évalué à 2.

        Pas cohérent!

        On dit plutôt : "Je trouve ces deux points contradictoires."

        Parce que moi, j'ai une analyse différente de la tienne et je vois pas de contradiction.

        Un benchmark est rarement un bon point d'entrée je trouve

        Pour moi ça veut dire qu'il faut pas se baser sur les performances en premier lieu. Il y a d'autres point importants autres que la performance. Tel que "la philosophie derrière".

        2 aspects importants […] et le mono-threading pour ne pas payer le coût du thread-safe par défaut.

        Pour moi, "importants" ici indique les deux aspects ont du poids dans le design de la bibliothèque et donc a des conséquences pour ce qui est de l'utilisation, performance et possibilité d'utilisation.
        Et non que ces points ont du poids dans la prise de décision d'utiliser une lib plutôt qu'une autre (ça dit pas non plus que ça a pas de poids, ça dit juste rien sur ça)

        De plus, "le mono-threading pour ne pas payer le coût du thread-safe par défaut" n'est à mon sens pas une conclusion ou un volonté de barmic mais une paraphrase de la description de Julien Jorge sur sa propre lib, pour rappeler les points de design de cette dernière :

        mono-thread : je n'ai quasiment jamais eu besoin de partager un signal entre plusieurs threads. Le mono-thread est le cas général, le multi-thread le cas particulier. Je préfère faire un effort pour gérer la synchronisation dans le peu de cas particuliers plutôt que de la payer pour rien dans le cas général.

        .

        Il te faut donc décider si la perf est importante (et la tu as besoin d'un benchmark) ou pas (et la tu t'en fous que la lib soit mono-thread ou pas).

        Ou un mélange des deux ? C'est souvent une histoire de curseur les décisions dans la vie.
        L'idée ici est justement de dire que les perf c'est peut-être important mais qu'il y a d'autres choses a prendre en compte.

        Matthieu Gautier|irc:starmad

        • [^] # Re: Remarques

          Posté par  . Évalué à 2.

          Tu as parfaitement compris mon point :)

  • # ça remonte

    Posté par  . Évalué à 8.

    Merci pour ce journal qui remonte le niveau technique du site, puisque des visiteurs s'en plaignaient…

    ⚓ À g'Auch TOUTE! http://afdgauch.online.fr

  • # Et Qt ?

    Posté par  (site Web personnel) . Évalué à 6.

    Je sais bien que Qt est un peu plus qu'une lib de signal/slot. Mais c'est LA lib qui m'a fait découvrir le concept (il y a bien longtemps).

    Matthieu Gautier|irc:starmad

    • [^] # Re: Et Qt ?

      Posté par  . Évalué à 2.

      Je suis peu familier de Qt mais si je me souviens bien leurs signaux font partie des extensions au langage, du coup je doute que l'auteur du benchmark puisse l'intégrer facilement et par conséquent je ne le mets pas non plus dans les tests.

      • [^] # Re: Et Qt ?

        Posté par  . Évalué à 2.

        À défaut de pouvoir tester Qt, sais-tu quelles catégories de tests (parmi les 4 que tu cites dans ton article) il remplirait ?

      • [^] # Re: Et Qt ?

        Posté par  (site Web personnel) . Évalué à 2.

        Oui et non. C'est une extension qui génère du code C++. Une fois le code généré, un compilateur classique fait très bien l'affaire.

  • # Signal

    Posté par  . Évalué à 10.

    Tout d'abord, très bon article, merci !

    Il y en a tellement qu'on estime aujourd'hui qu'il existe environ 1,14 bibliothèques de ce type pour chaque développeur C++.

    Alors, oui, mais ça me paraît normal. Mes étudiants ont eu droit cette année à ce sujet : développer une bibliothèque de signal en C++ (sur 2 semaines). C'est un peu le genre de bibliothèque qui permet de manipuler plein de notions de C++. Je vais pas dire que c'est un Hello World mais disons que ça permet de balayer assez large sans être trivial tout en étant concis.

    Après, sur les tests, c'est assez marrant, parce que je n'ai pas eu les mêmes exigences que toi. Par exemple, je leur ai imposé de conserver l'ordre d'enregistrement pour l'appel des slots. En revanche, je n'ai absolument rien dit sur le fait de pouvoir ajouter et supprimer pendant un déclenchement de signal. Mais c'est cool, ça me donne des idées pour la prochaine fois. On était parti sur SimpleSignal (lss) et donc on avait mis des combineurs. Et on s'est amusé à tester les codes avec des objets bizarres (genre des non-copiables, ou non-déplaçable, etc) à la fois dans les paramètres et dans les types de retour : beaucoup de code d'étudiants ne compilait même pas. Il y a aussi l'utilisation de reference_wrapper pour le slot (genre avec un std::ref) qui fait couler beaucoup d'encre rouge.

    Bref, je vais garder ton analyse sous le coude pour la prochaine fois, et je pense que je vais m'en inspirer très largement.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n’en sommes pas responsables.