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:part3 [2021/11/08 18:11] bmonsuez [Question 4 :] |
in204:tds:sujets:td9:part3 [2022/11/18 10:49] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Partie 3 : Exécution à la compilation ====== | + | ====== Exécution asynchrone ====== |
[[in204:tds:sujets:td9|TD9]] | [[in204:tds:sujets:td9|TD9]] | ||
Line 5: | Line 5: | ||
===== Références===== | ===== Références===== | ||
+ | [[http://en.cppreference.com/w/cpp/thread/async|std::async]] [[http://en.cppreference.com/w/cpp/thread/future|std::future]] | ||
- | ===== Question 1 : ===== | + | ===== Question n°1 ===== |
- | + | ||
- | Nous nous intéressons à l'estimation du temps de calcul des fonction ''factorial'' et des fonctions ''power_by_int'' que nous avons défini précédemment. | + | |
+ | Nous considérons la fonction suivante qui calcule les décimales de « e ». | ||
<code cpp> | <code cpp> | ||
- | auto fn = estimate_function_time(factorial, 100); | + | std::string computeE(int numberOfDigits) |
- | std::cout << "Computing fact(100)=" << fn .second << " in " << fn.first.count() << " ticks.\n"; | + | |
- | auto pw = estimate_function_time(power_by_int<long double>, 1.0002, 1000000); | + | |
- | std::cout << "Computing 1.02^1000000=" << pw.second << " in " << pw.first.count() << " ticks.\n"; | + | |
- | </code> | + | |
- | + | ||
- | Ceci nous retourne le temps mis pour calculer la fonction ''factorial'' et pour la fonction ''power_by_int''. | + | |
- | + | ||
- | Expérimenter. | + | |
- | + | ||
- | ===== Question 2 : ===== | + | |
- | + | ||
- | + | ||
- | Nous souvaitons indiquer au compilateur qu'il peut calculer au moment de la compilation les expressions si celles-ci sont constantes. | + | |
- | + | ||
- | Pour ce faire nous ajoutons le mot-clé ''constexpr'' devant la fonction ou l'expression dont la valeur peut-être exécuté au moment de la compilation. | + | |
- | + | ||
- | Ainsi, nous pouvons indiquer que les deux fonctions ''factorial'' et ''power_by_int'' peuvent être calculer au moment de la compilation si les arguments sont des valeurs définies au moment de la compilation. | + | |
- | + | ||
- | <code cpp> | + | |
- | constexpr long double factorial(int n) | + | |
{ | { | ||
- | return n == 0 ? 1 : n * factorial(n - 1); | + | int sizeOfTable = numberOfDigits + 9; |
- | } | + | int* table = (int*)_alloca(sizeOfTable * sizeof(numberOfDigits)); |
+ | table[0] = 0; | ||
+ | table[1] = 2; | ||
+ | for (int i = sizeOfTable - 1; i > 0; i--) { | ||
+ | table[i] = 1; | ||
+ | } | ||
- | template<class numericalT> | + | std::ostringstream output; |
- | constexpr numericalT power_by_int(numericalT x, int y) | + | int x = 0; |
- | { | + | table[1] = 2; |
- | numericalT result = (numericalT)1.0; | + | for (; sizeOfTable > 9; sizeOfTable -- ) |
- | while (y-- > 0) | + | { |
- | result *= x; | + | for (int i = sizeOfTable - 1; i > 0; i--) |
- | return result; | + | { |
+ | table[i] = x % i; | ||
+ | x = 10 * table[i - 1] + x / i; | ||
+ | } | ||
+ | output << x; | ||
+ | } | ||
+ | return output.str(); | ||
} | } | ||
</code> | </code> | ||
- | Ceci autorise le compilateur a compilé l'expression au moment de la compilation. | + | Implanter la fonction et vérifier que celle-ci fonctionne correctement. |
- | + | <hidden Correction> | |
- | ===== Question 1 : ===== | + | |
- | + | ||
- | Tester le code suivant: | + | |
<code cpp> | <code cpp> | ||
+ | #include<sstream> | ||
+ | #include<iostream> | ||
- | auto fn = estimate_function_time(factorial, 100); | + | std::string computeE(int numberOfDigits) |
- | std::cout << "Computing fact(100)=" << fn.second << " in " << fn.first.count() << " ticks.\n"; | + | |
- | + | ||
- | auto pw = estimate_function_time(power_by_int<long double>, 1.0002, 1000000); | + | |
- | std::cout << "Computing 1.02^100000=" << pw.second << " in " << pw.first.count() << " ticks.\n"; | + | |
- | + | ||
- | </code> | + | |
- | + | ||
- | + | ||
- | A votre avis ? Est-ce que le compilateur à générer le code au moment de la compilation ? | + | |
- | + | ||
- | + | ||
- | ===== Question 2 : ===== | + | |
- | + | ||
- | En fait, nous pouvons aider naivement le compilateur en faisant bien apparaître le paramètre constant : | + | |
- | + | ||
- | <code cpp> | + | |
- | factorial_100 = []() { return factorial(100); }; | + | |
- | fn = estimate_function_time(factorial_100); | + | |
- | std::cout << "Computing fact(100)=" << fn.second << " in " << fn.first.count() << " ticks.\n"; | + | |
- | + | ||
- | power_10002_100000 = []() { return power_by_int<long double>, 1.0002, 100000); }; | + | |
- | pw = estimate_function_time(power_10002_100000); | + | |
- | std::cout << "Computing 1.02^1000000=" << pw.second << " in " << pw.first.count() << " ticks.\n"; | + | |
- | </code> | + | |
- | + | ||
- | + | ||
- | Est-ce que cela améliore les résultats ? Tenter d'expliquer pourquoi ? | + | |
- | + | ||
- | + | ||
- | ===== Question 3 : ===== | + | |
- | + | ||
- | Il faut imposer que l'évaluation se fasse à la compilation. Pour ce faire, nous pouvons forcer à ce que l'expression soit évaluée en ajoutant l'attribue ''constexpr'' à la variable résultat du calcul. | + | |
- | + | ||
- | Ainsi le code suivant : | + | |
- | + | ||
- | <code cpp> | + | |
- | constexpr long double factorial(int n) | + | |
{ | { | ||
- | return n == 0 ? 1 : n * factorial(n - 1); | + | int sizeOfTable = numberOfDigits + 9; |
- | } | + | int* table = (int*)_alloca(sizeOfTable * sizeof(numberOfDigits)); |
+ | table[0] = 0; | ||
+ | table[1] = 2; | ||
+ | for (int i = sizeOfTable - 1; i > 0; i--) { | ||
+ | table[i] = 1; | ||
+ | } | ||
- | int main() | + | std::ostringstream output; |
- | { | + | int x = 0; |
- | auto res = factorial(100); | + | table[1] = 2; |
- | std::cout << res << "\n"; | + | for (; sizeOfTable > 9; sizeOfTable--) |
+ | { | ||
+ | for (int i = sizeOfTable - 1; i > 0; i--) | ||
+ | { | ||
+ | table[i] = x % i; | ||
+ | x = 10 * table[i - 1] + x / i; | ||
+ | } | ||
+ | output << x; | ||
+ | } | ||
+ | return output.str(); | ||
} | } | ||
- | </code> | ||
- | Indique que le compilateur peut effectué le calcul au moment de la compilation mais la plupart des compilateurs ne le font que partiellement. | ||
- | |||
- | Pour forcer, il est possible de déclarer la variable ''res'' comme étant une variable stockant le résultat d'une expression constante : | ||
- | |||
- | <code cpp> | ||
int main() | int main() | ||
{ | { | ||
- | auto res = factorial(100); | + | std::string value = computeE(100); |
- | std::cout << res << "\n"; | + | std::cout << "e with " << 100 << " decimals\n" << value << std::endl; |
} | } | ||
</code> | </code> | ||
+ | </hidden> | ||
+ | ===== Question n°2 ===== | ||
- | Dans ce cas, le compilateur va lancer l'évaluation de ''factorial(100)'' au moment de la compilation, en effet, il doit s'assurer que ''res'' est une variable stockant le résultat d'une expression constante, le seul moyen de le vérifier est de calculer le résulat. | + | Nous constatons que calculer 10000 ou 20000 décimales de e, cela prend du temps. Nous souhaitons transformer cela en une fonction asynchrone à l’aide de la fonction [[http://en.cppreference.com/w/cpp/thread/async|std::async]]. |
- | Ainsi on force bien l'évaluation au moment de l'exécution. | + | Ecrire le code transformant la précédente fonction en une fonction asynchrone en utilisant la fonction [[http://en.cppreference.com/w/cpp/thread/async|std::async]]. |
- | Modifier le code des ''lambda'' expressions pour mettre en oeuvre ce mécanisme et estimer les temps de calculs. | + | Au lieu d'appeller directement la fonction ''computeE'', nous appellons la fonction ''computeE'' au travers d'un appel à la fonction ''std::async'' qui va exécuter la fonction ''computeE'' de manière asynchone et retourner un objet de type ''std::future<string>'' qui va permettre de savoir si le résultat et disponible et aussi d'avoir la valeur du résultat quand celui-ci sera disponible. |
<hidden Correction> | <hidden Correction> | ||
- | auto c_factorial_100 = []() { constexpr auto res = factorial(100); return res; }; | ||
- | fn = estimate_function_time(c_factorial_100); | ||
- | std::cout << "Computing fact(100)=" << fn.second << " in " << fn.first.count() << " ticks.\n"; | ||
- | |||
- | auto c_power_10002_100000 = []() { constexpr auto res = power_by_int<long double>(1.0002, 100000); return res; }; | ||
- | pw = estimate_function_time(power_10002_100000); | ||
- | std::cout << "Computing 1.02^1000000=" << pw.second << " in " << pw.first.count() << " ticks.\n"; | ||
- | |||
- | </hidden> | ||
- | |||
- | |||
- | ===== Question 4 : ===== | ||
- | |||
- | Tester le code suivant avec un compilateur C++20: | ||
- | |||
<code cpp> | <code cpp> | ||
- | template<double value, int power> | + | void display(std::future<std::string>& aFutureValue, int theDecimals) |
- | struct constant_value | + | |
{ | { | ||
- | static constexpr long double constant() { constexpr auto result = power_by_int(value, power); return result; } | + | aFutureValue.wait(); |
- | }; | + | std::cout << "e with " << theDecimals << " decimals\n" << aFutureValue.get() << std::endl; |
+ | } | ||
+ | int main() | ||
+ | { | ||
+ | std::future<std::string> eWidth20000 = std::async(std::launch::async, &computeE, 20000); | ||
+ | std::future<std::string> eWidth100000 = std::async(std::launch::async, &computeE, 100000); | ||
+ | display(eWidth20000, 20000); | ||
+ | display(eWidth100000, 100000); | ||
+ | } | ||
</code> | </code> | ||
+ | </hidden> | ||
- | et appeller cette fonction comme suit: | ||
- | <code cpp> | + | ===== Question n°3 ===== |
- | auto pw_c = estimate_function_time(precomputed_values<1.0002, 100000>::pow); | + | |
- | std::cout << "Computing 1.02^1000000=" << pw_c.second << " in " << pw_c.first.count() << " ticks.\n"; | + | |
- | }</code> | + | |
- | Expliquer ce qui se passe ? Et pourquoi ce résultat. | + | Lancer deux calculs asynchrones, l’un calculant les 1000 premières décimales, l’autre les 10000 premières décimales et afficher les résultats dès que ceux-ci sont disponibles. |
<hidden Correction> | <hidden Correction> | ||
- | + | La fonction ''main'' précédente effectue déjà ce calcul. | |
- | + | ||
- | Les paramètres ''valeurs'' de la classe ''pre_computed_values'' sont des paramètres constants, donc la fonction ''precomputed_values'' peut-être évaluée au moment de la compilation. | + | |
- | + | ||
- | De plus, comme nous avons imposé que le résultat soit calculé au moment de la compilation en plaçant le résultat de la fonction dans une variable dont la valeur est calculée au moment de la compilation, alors le compilateur évalue l'expression au moment de la compilation. | + | |
- | + | ||
- | <code cpp> | + | |
- | template<double value, int power> | + | |
- | struct precomputed_values | + | |
- | { | + | |
- | static long double pow() { constexpr auto result = power_by_int<long double>(value, 100000); return result; } | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Quand nous mesurons le temps requis, c'est uniquement le temps requis pour retourner le résulat et non plus le temps requis pour calculer l'expression puisque celle-ci a déjà été calculée. | + | |
- | + | ||
</hidden> | </hidden> | ||
- | |||
- | ====== Navigation ====== | ||
- | [Partie 2: [.part2|Mesurer le temps passé par une fonction]] | ||