Le C++… J’en ai déjà fait il y a quelques années, mais je m’y suis réellement mis à fond cette année. C’est cool, j’aime bien, enfin bref. Dans un de mes précédents articles je vous parlais d’exécuter plusieurs « await » (façon de parler) à la fois en C# (ici). Aujourd’hui, je vous propose de quelque chose de plus poussé en C++ natif.

Cette année dans mon école, SUPINFO, on a eu à réaliser un moteur de recherche en projet de fin d’année. Dans ce projet, le back-end devait être impérativement être fait en C++ natif. On a passé 5 mois sur le projet au complet, en appliquant une sorte de SCRUM simplifié pour s’adapter à nos horaires académiques. J’ai vraiment adoré ce projet, toujours bonne ambiance, aucun stress, projet bouclé avec 3 semaines d’avance, bref, un scénario idéal.

Ma Dream Team

 

THE Search Engine

Oui ça fait très « Apple » 🙂

Problématique

Lorsque l’on s’est occupé du back-end, on s’est fait la réflexion suivante : lorsque l’on indexera un site internet, on va détecter des url à indexer dans chacune des pages au fur et à mesure. Histoire de gagner en vitesse, on va utiliser des Threads pour analyser plusieurs pages internet à la fois. Seulement voilà, si je prends l’un de mes sites préférés (MSDN ? \o/), je vais trouver 200 url par pages. 200 Threads exécutés à la fois, c’est juste impensable ! Le CPU va souffrir, selon les traitements, la RAM va en prendre un coup également. Bref, on ne peut pas laisser faire le programme ainsi.
Ainsi, deux solutions s’ouvraient à nous :

Osef la vitesse ! On fait une page à la fois !

Ou bien

On va équilibrer vitesse et performance.

Challenge Accepted !

L’idée

On va se faire un gestionnaire de Threads ! Dans le cadre de ce projet académique, histoire d’apprendre vraiment, on n’a pas voulu utiliser de librairie externe telle que BOOST. En revanche, on a utilisé un peu la PPL (Parallel Patterns Library) du coup. Dans le principe, notre technique pour faire ce gestionnaire de Threads est assez simple, mais plusieurs paramètres vont rentrer en compte :

  1. A chaque nouveau thread créé, on lui attribue une priorité : doit-il s’exécuter le plus tôt possible (IMPORTANT), ou peut-il attendre un peu (NORMAL) ?
  2. A chaque nouveau thread créé, on le met dans une file d’attente. Il n’est donc pas tout de suite exécuté.
  3. Soit N le nombre de cœurs dans le processeur de la machine. Notre gestionnaire exécutera N Thread à la fois et pas un de plus.
  4. Dès qu’un Thread a terminé son exécution, on lance le prochain Thread le plus important (déterminé selon sa priorité et son ancienneté dans la file d’attente). Il n’est donc plus question d’attendre la fin de tous les threads contrairement à mon précédent article en C#.
  5. Si un Thread prend trop de temps à s’exécuter (et empêche donc d’autres de se lancer), on lui demande de s’arrêter dès que possible.

Le résultat

Il est là (en bas de l’article) ! Et ça fonctionne super bien 🙂

Le système est composé de deux classes :

  1. ThreadManager : c’est le gestionnaire en lui-même. Il gère la file d’attente, les priorités.
  2. Task : représente une fonction à exécuter avec gestion du timeout.

Voici un exemple d’utilisation :

Le code ci-dessus créer un nouveau gestionnaire de Threads. Le premier paramètre définit si les tâches doivent être exécutées manuellement ou pas (ici, elles sont automatiquement gérées dès qu’on ajoutait une tâche à exécuter). Le second paramètre définit le timeout en secondes.

Si vous mettez le premier paramètre à True, vous devrez utiliser la commande suivante pour exécuter la file d’attente.

Ensuite, pour ajouter une nouvelle tâche à la file d’attente, c’est très simple :

 

Ici, on a simplement ajouté une tâche « normale » (non prioritaire) en file d’attente. La méthode « AMethodToRun » avec les paramètres donnés ensuite. Vous pouvez mettre un nombre indéfini de paramètres (du moment que ça respecte le même nombre que ce que votre méthode prend).

Ça revient donc à exécuter la méthode suivante de manière asynchrone dans une file d’attente :

Conclusion

En deux lignes de code je peux créer une file d’attente de Thread et ainsi gagner en vitesse et en même temps en performance. Je terminerais par un petit comparatif des avantages et désavantages de cette classe :

Avantages :

  1. Simple.
  2. Fonctionne avec des pointeurs.
  3. Gère les priorités, pratique si vous voulez par exemple donner d’avantage la main à un sous-programme qu’à un autre.
  4. Gère le timeout.
  5. Le CPU et la Ram à un instant T est moins sollicité que si tout était lancé d’un coup. Le nombre de Threads lancés à la fois dépend du nombre de cœurs virtuels de la machine.

Désavantages :

  1. Ne fonctionne qu’avec des Void.
  2. Selon la manière dont on définit les priorités, on peut attendre un bon moment avant qu’un Thread non prioritaire soit exécuté.
  3. Peu d’intérêt sur des petits travaux asynchrone.

Je vous laisse un projet exemple (sous Visual Studio) avec les sources ci-dessous. Il y a sûrement des optimisations possibles, d’autant plus que ce moteur de recherche (avec ce gestionnaire de Threads) est mon premier projet vraiment concret en C++.

Je ne l’ai pas testé sous Linux, mais apparemment il n’utilise pas de librairie propre à Windows, donc ça devrait fonctionner.

Faites-moi savoir si vous trouvez de quoi améliorer ça 🙂 .

Sur ce, je vous dis à très bientôt ! Je pars en stage à Microsoft Corp aux Etats-Unis du 28 Juin 2014 au 20 Septembre. J’intègre l’équipe en charge du développement des outils de télémétrie de Visual Studio. J’ai vraiment hâte d’y être, même si je patauge (et je pataugerais) ça s’annonce comme étant une super expérience, surtout que je vais pouvoir développer l’outil qui a été mon exemple et mon inspiration pour SoftwareZator, c’est comme une belle récompense de pouvoir travailler là-dessus. Je pense faire un article/vidéo d’ici quelques temps sur toute la démarche qui s’est faite depuis Mai 2013 dernier pour pouvoir partir, ce que propose Microsoft…etc. Aussi, soyez sûr que je vous ferais partager la vie d’un stagiaire à Microsoft Corp (sûrement un peu différente des stages de Microsoft France).

Codes sources

Télécharger le projet TasksQueue