Comparatif de boucles à rythme régulier et à rythme constant

Le code de cet exemple est naïf, mais vise à illustrer la différence entre deux programmes, l'un itérant à rythme régulier et l'autre itérant à rythme constant. Il utilise les outils de C++ 14, mais peut être ramené à C++ 11 en remplaçant le littéral 1s par l'expression seconds{ 1 }.

Nous comparerons ici brièvement deux programmes réalisant une même tâche, l'un à rythme régulier et l'autre à rythme constant. J'ai cherché à réduire le problème à sa plus simple expression, soit :

Les deux exemples sont identiques, outre un tronçon dans le programme principal (fonction main()) situé entre les commentaires DEBUT et FIN.

Exemple à rythme régulier Exemple à rythme constant

L'exemple à rythme régulier suit. Exprimé ainsi, nous attendons un appel à faire_tache() une fois par seconde, mais avec un délai variant quelque peu entre chaque appel, en fonction du temps requis pour l'exécution de preparer_tache().

L'exemple à rythme constant suit. Exprimé ainsi, nous attendons un appel à faire_tache() une fois par seconde, mais avec un délai d'une seconde entre chaque appel, sans égard au temps requis pour l'exécution de preparer_tache().

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <random>
using namespace std; // ne faites pas ça dans un .h!!!
using namespace std::chrono; // idem!!!

void faire_tache(system_clock::time_point cur,
                 system_clock::time_point init) {
   auto dt = duration_cast<milliseconds>(cur - init).count();
   cout << "Coucou! (" << dt << " ms.)" << endl;
}

void preparer_tache(mt19937 &prng) {
   uniform_int_distribution<> dt{ 500, 600 };
   this_thread::sleep_for(milliseconds{ dt(prng) });
}

int main() {
   random_device rd;
   mt19937 prng{ rd() };
   atomic<bool> fini  { false };
   auto lire_touche = thread([&fini]() {
      char c;
      cin >> c;
      fini = true;
   });
   const auto init = system_clock::now();
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <random>
using namespace std; // ne faites pas ça dans un .h!!!
using namespace std::chrono; // idem!!!

void faire_tache(system_clock::time_point cur,
                 system_clock::time_point init) {
   auto dt = duration_cast<milliseconds>(cur - init).count();
   cout << "Coucou! (" << dt << " ms.)" << endl;
}

void preparer_tache(mt19937 &prng) {
   uniform_int_distribution<> dt{ 500, 600 };
   this_thread::sleep_for(milliseconds{ dt(prng) });
}

int main() {
   random_device rd;
   mt19937 prng{ rd() };
   atomic<bool> fini { false };
   auto lire_touche = thread([&fini]() {
      char c;
      cin >> c;
      fini = true;
   });
   const auto init = system_clock::now();
   //
   // DEBUT
   //
   while (!fini) {
      const auto avant = system_clock::now();
      const auto echeance = avant + 1s;
      preparer_tache(prng);
      auto cur = system_clock::now();
      faire_tache(cur, init);
      this_thread::sleep_until(echeance);
   }
   //
   // FIN
   //
   //
   // DEBUT
   //
   preparer_tache(prng);
   while (!fini) {
      const auto avant = system_clock::now();
      const auto echeance = avant + 1s;
      faire_tache(avant, init);
      preparer_tache(prng);
      this_thread::sleep_until(echeance);
   }
   //
   // FIN
   //
   lire_touche.join();
}
   lire_touche.join();
}

L'affichage obtenu à l'exécution de ce programme pourrait être comme suit.

L'affichage obtenu à l'exécution de ce programme pourrait être comme suit.

Coucou! (501 ms.)
Coucou! (1502 ms.)
Coucou! (2515 ms.)
Coucou! (3532 ms.)
Coucou! (4600 ms.)
Coucou! (5597 ms.)
q
Appuyez sur une touche pour continuer...
Coucou! (586 ms.)
Coucou! (1586 ms.)
Coucou! (2586 ms.)
Coucou! (3586 ms.)
Coucou! (4586 ms.)
Coucou! (5586 ms.)
q
Appuyez sur une touche pour continuer...

Remarquez que le temps écoulé entre chaque appel varie un peu, parfois à la hausse et parfois à la baisse.

Remarquez que le temps écoulé entre chaque appel est exactement le même, ou tout près (il peut y avoir une variation minime en fonction de la qualité de l'implémentation).

Bien entendu, les deux modèles ont leur place, mais cette illustration met en relief l'influence de certains choix de design algorithmiques dans la capacité d'un programme à réaliser (ou non) une tâche en un point précis d'une itération.


Valid XHTML 1.0 Transitional

CSS Valide !