This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
in204:tds:sujets:td9:part1 [2021/11/06 14:44] bmonsuez |
in204:tds:sujets:td9:part1 [2022/11/18 10:49] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Partie 1 : Manipuler un ensemble d'arguments ====== | + | ====== Création & Manipulation de Processus léger ====== |
- | ===== Question 1 ===== | + | [[in204:tds:sujets:td9|TD9]] |
- | Ecrire une fonction ''print'' qui prend un nombre libre d'entiers en paramètres et qui affiche ces entiers sur la console. | + | ===== Références===== |
- | Pour ce faire, nous vous conseillons d'autiliser la classe ''std::initializer_list<T>'' comme argument de la fonction. | + | [[http://en.cppreference.com/w/cpp/thread/thread|std::thread]] |
- | Un [[https://en.cppreference.com/w/cpp/utility/initializer_list|std::initializer_list<T>]] permet d'accéder à la une liste de valeurs de type ''T'' qui est écrit sous la forme suivante : | + | ===== Question n°1 ===== |
+ | |||
+ | Créer un processus léger qui est associé à une fonction simple. | ||
<code cpp> | <code cpp> | ||
- | std::initializer_list<int> list_of_values = {1, 3, 4, 2 }; | + | #include<iostream> |
- | </code> | + | #include<thread> |
- | Cette liste de valeur n'est pas modifiable. Il est seulement possible de la lire en lecture comme un containeur classique. Les fonctions ''begin()'', ''end()'' ainsi que ''size()'' permettent d'accéder aux valeurs stockées dans la liste. | + | void simple_method() |
+ | { | ||
+ | int i = 5; | ||
+ | int x = 10; | ||
+ | int result = i * x; | ||
+ | std::cout << "This code calculated the value " | ||
+ | << result << " from thread ID: " | ||
+ | << std::this_thread::get_id() << "\n"; | ||
+ | } | ||
- | <hidden Correction> | ||
- | <code cpp> | + | int main() |
- | void print(std::initializer_list<int> arguments) | + | |
{ | { | ||
- | auto it = arguments.begin(), | + | std::thread simpleThread(&simple_method); |
- | end_it = arguments.end(); | + | std::cout << "Main thread is executing and waiting\n"; |
- | if (it != end_it) | + | simpleThread.join(); |
- | { | + | std::cout << "Alternate thread has terminated.\n"; |
- | std::cout << *it; | + | return 0; |
- | while (++it != end_it) | + | |
- | std::cout << ", " << *it; | + | |
- | } | + | |
} | } | ||
+ | |||
</code> | </code> | ||
- | </hidden> | + | Exécuter le code et analyser la sortie. Commenter celle-ci, notamment au regard de la documentation de la classe [[http://en.cppreference.com/w/cpp/thread/thread|std::thread]]. |
- | ===== Question 2 ===== | + | <hidden Correction> |
- | Généraliser cette fonction à d'autres types que les types entiers. | + | Un objet ''std::thread'' correspondant à un processus léger est créé et est associé au code la méthode ''simple_method''. Ce processus démarre immédiatement et lance le calcul. L'exécution du code principal se poursuit et le message ''Main thread is executing and waiting'' est affiché. La ligne suivant attend que l'exécution du processus léger associé à l'objet ''std::thread'' termine. Une fois que ce processus a terminé, l'exécution du processus principal continue, affiche le message ''Alternate thread has terminated'' et rend la main. |
+ | </hidden> | ||
- | Cette fois-ci, nous attaquons les choses un peu plus complexes. En effet, pour cela, vous allez utiliser les [[https://en.cppreference.com/w/cpp/language/parameter_pack|packs de paramètres]] des fonctions et des classes templatées. | ||
- | Quand nous écrivons : | + | ===== Question n°2 ===== |
- | * ''template<class ...Args> f(Args... arguments)'', cela signifie que la fonction prend comme arguments un ensemble d'arguments qui ont des types différents. La variable ''arguments'' fait référence à l'ensemble des valeurs ayant chacun un type différent, c'est pour cela que l'on parle de **pack de paramètres**. Attention, il n'est pas possible d'accéder aux données individuelles présentes dans le **pack de paramètres**. | + | Ecrire un programme qui lance les deux calculs suivants en parallèle, le premier dans un processus léger secondaire, le premier dans le processus léger principal : |
- | * l'opérateur ''sizeof...(Args)'' permet d'obtenir le nombres d'arguments passés à la fonction ou spécifié comme paramètres de la classe. | ||
- | |||
- | * En fait, les packs de paramètres servent à être passés directement à une fonction. Ainsi nous pouvons écrire le code suivant : | ||
<code cpp> | <code cpp> | ||
- | void number_of_args() { std::cout << "no arguments"; } | + | void worker_process(int numberOfIterations) |
- | template<classT> void number_of_args(T) { std::cout << "one argument"; } | + | |
- | template<classT1, class T2> void number_of_args(T1, T2) { std::cout << "two arguments"; } | + | |
- | + | ||
- | template<class ...Args> | + | |
- | void func(Args... arguments) | + | |
{ | { | ||
- | return number_of_args(arguments...); | + | for (int i = 1; i < numberOfIterations; i++) |
+ | { | ||
+ | std::cout << "Worker Thread: " << i << "\n"; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | va appeller en fonction du nombre d'arguments l'une des fonctions ''number_of_args''. | ||
- | |||
- | Nous pouvons désormais imaginer une approche récursive pour compter le nombre d'arguments en implantant le code suivant : | ||
+ | et | ||
<code cpp> | <code cpp> | ||
- | template<class ...Args> | + | void main_process() |
- | int number_of_args(T first_argument, Args... arguments) | + | |
{ | { | ||
- | return 0; | + | for (int i = 1; i < 1000; i++) |
- | } | + | { |
- | template<class T, class ...Args> | + | std::cout << "Primary Thread: " << i << "\n"; |
- | int number_of_args(T first_argument, Args... arguments) | + | } |
- | { | + | |
- | return 1 + number_of_args(arguments); | + | |
} | } | ||
</code> | </code> | ||
- | + | ||
- | L'appel de la fonction ''number_of_args'' qui suit : | + | Tester le code. |
- | <code cpp> | + | |
- | std::cout << number_of_args(1, "e", 2.0, 'c') << "\n"; | + | |
- | </code> | + | |
- | + | ||
- | retournera ''4''. | + | |
- | + | ||
- | ==== Question 2.1 ==== | + | |
- | + | ||
- | Expliquer comment le code a été exécuté ? | + | |
- | + | ||
- | + | ||
- | ==== Question 2.2 ==== | + | |
- | + | ||
- | En vous inspirant du code précédant, proposer une fonction ''print'' qui prend un nombre variable de paramètres qui peuvent avoir des types différents et qui imprime cette liste de paramètres sur la console. | + | |
<hidden Correction> | <hidden Correction> | ||
- | template<class T> | + | Nous pouvons écrire le code suivant qui crée deux processus légers, le premier exécutant la fonction ''worker_process'' avec comme paramètre ''10000'' et le second exécutant la fonction ''main_proc''. |
- | void print(T first_argument) | + | |
- | { | + | |
- | std::cout << first_argument; | + | |
- | } | + | |
- | template<class T, class ...Args> | + | <code cpp> |
- | void print(T first_argument, Args... arguments) | + | int main() |
{ | { | ||
- | std::cout << first_argument; | + | std::thread worker_proc(&worker_process, 10000); |
- | if (sizeof...(Args) > 0) | + | std::thread main_proc(&main_process); |
- | std::cout << ", "; | + | worker_proc.join(); |
- | print(arguments...); | + | main_proc.join(); |
} | } | ||
+ | </code> | ||
- | </hidden> | + | L'exécution commence par la tâche ''worker_proc'' et nous devons voir certains messages de la tâche ''main_proc'', nous voyons cependant que les messages ne sont pas très entrelacés, ce qui est du au fait que l'on alloue un temps d'utilisation de la console à chacun des deux processus légers. |
+ | Pour mettre un peu d'entrelacement, nous pouvons ajouter un peu de temps entre les affichaches, par exemple 1ns pour le premier processus et 10ns pour le second. Ceci nous donne le code suivant: | ||
- | ====== Partie 2 :Mesuré le temps passé par une fonction ====== | + | <code cpp> |
+ | #include<iostream> | ||
+ | #include<thread> | ||
+ | #include <chrono> | ||
- | [[in204:tds:sujets:td9|TD9]] | ||
- | ===== Références===== | ||
- | [[http://en.cppreference.com/w/cpp/thread/thread|std::thread]] | + | void worker_process(int numberOfIterations) |
- | + | ||
- | + | ||
- | ====== Partie n°1 ====== | + | |
- | + | ||
- | Nous souhaitons mesurer le temps de calcul d'une fonction. Pour ce faire, nous souhaitons créer une fonction : | + | |
- | - qui va prendre en argument la fonction que nous souhaitons exécuter, | + | |
- | - les arguments que nous devons passer à cette fonction, | + | |
- | - qui va lancer un chronomètre, | + | |
- | - qui va lancer la fonction | + | |
- | - qui va récupérer le résultat de la fonction, | + | |
- | - qui va estimer le temps passé par le temps de la fonction, | + | |
- | - qui va retourner à la fois le résultat de la fonction mais aussi le temps passé par la fonction. | + | |
- | + | ||
- | ===== Question n°1 ===== | + | |
- | + | ||
- | Pour simplifier la conception, écrivez dans un premier la fonction qui appelle la fonction factorielle qui suit: | + | |
- | et qui va exécuter cette fonction | + | |
- | + | ||
- | <code cpp> | + | |
- | int factorial(int n) | + | |
{ | { | ||
- | return n <= 1 ? 1 : (n * factorial(n - 1)); | + | for (int i = 1; i < numberOfIterations; i++) |
+ | { | ||
+ | std::cout << "Worker Thread: " << i << "\n"; | ||
+ | std::this_thread::sleep_for(10ns); | ||
+ | } | ||
} | } | ||
- | </code> | ||
- | La fonction aura le squelette suivant: | + | void main_process() |
- | <code> | + | |
- | int estimate_time(int n) | + | |
{ | { | ||
- | // Code pour lancer le chronomètre | + | for (int i = 1; i < 1000; i++) |
- | int result = factorial(n); | + | { |
- | // Code pour calculer le temps écoulé et affiché celui-ci. | + | using namespace std; |
- | return result; | + | std::cout << "Primary Thread: " << i << "\n"; |
+ | std::this_thread::sleep_for(1ns); | ||
+ | } | ||
} | } | ||
- | </code> | ||
- | ===== Question n°2 ===== | + | int main() |
- | + | ||
- | Transformer la fonction précédente <code>estimate_time</code> pour qu'elle prenne en argument une fonction arbitraire à un argument et un résultat. | + | |
- | + | ||
- | Il faudra penser à utiliser un modèle (template) de fonctions. | + | |
- | + | ||
- | <hidden Correction> | + | |
- | + | ||
- | Il suffit de modifier la fonction antérieure comme un fonction qui prend deux paramètres de types: | + | |
- | - le type correspondant à la fonction, | + | |
- | - le type correspondant au paramètre de la fonction. | + | |
- | + | ||
- | Et le tour est joué. Ceci donne le code suivant : | + | |
- | + | ||
- | <code cpp> | + | |
- | template<class Function, class T> | + | |
- | std::pair<std::chrono::high_resolution_clock::duration, long double> estimate_function_time(Function function, T argument) | + | |
{ | { | ||
- | auto starting_time = std::chrono::high_resolution_clock::now(); | + | std::thread worker_proc(&worker_process, 10000); |
- | auto result = function(argument); | + | std::thread main_proc(&main_process); |
- | auto elasped_time = std::chrono::high_resolution_clock::now() - starting_time; | + | worker_proc.join(); |
- | return std::make_pair(elasped_time, result); | + | main_proc.join(); |
} | } | ||
</code> | </code> | ||
</hidden> | </hidden> | ||
- | |||
- | |||
- | ===== Question n°3 ===== | ||
- | |||
- | Nous souhaitons désormais pouvoir prendre une fonction pouvant prendre plusieurs arguments comme la fonction puissance. | ||
- | |||
- | <code cpp> | ||
- | template<class T1, T2> | ||
- | T1 power(T2 x, int y) | ||
- | { | ||
- | T1 result = (T1)1.0 | ||
- | while(y-- > 0) | ||
- | result = result * (T1)x; | ||
- | return result; | ||
- | } | ||
- | </code> | ||
- | |||
- | Modifier le code de la fonction pour pouvoir prendre une telle fonction comme paramètre. | ||
- | |||
- | Estimer le temps nécesaire pour calculer par exemple : 1.02 ^ 10000000. | ||
- | |||
- | |||
- | ==== Question n°5 ==== | ||
- | |||
- | Créer une fonction nouvelle qui va appeller la fonction <code>estimate_time</code> pour calculer <code>x</code> fois le temps et retourné le temps moyen pour effectuer un "run" de la fonction. | ||
- | |||
- | Exécuter cette fonction pour les fonctions <code>power</code> et <code>factorial</code> précédemment définie. | ||
- | |||
- | ==== Question n°4 ==== | ||
- | |||
- | Que faut-il faire ajouter pour dire au compilateur que la fonction <code>factorial</code> ou que la fonction <code>power</code> peut-être exécutée au moment de l'exécution ? | ||
- | |||
- | Effectuer la modification. | ||
- | |||
- | Calculer le temps pris désormais par ces fonctions. | ||
- | |||
- | |||