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.