====== Partie 3 : Exécution à la compilation ======
[[in204:tds:sujets:td12_2023|TD12]]
===== Références=====
===== Question 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.
auto fn = estimate_function_time(factorial, 100);
std::cout << "Computing fact(100)=" << fn .second << " in " << fn.first.count() << " ticks.\n";
auto pw = estimate_function_time(power_by_int, 1.0002, 1000000);
std::cout << "Computing 1.02^1000000=" << pw.second << " in " << pw.first.count() << " ticks.\n";
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.
constexpr long double factorial(int n)
{
return n == 0 ? 1 : n * factorial(n - 1);
}
template
constexpr numericalT power_by_int(numericalT x, int y)
{
numericalT result = (numericalT)1.0;
while (y-- > 0)
result *= x;
return result;
}
Ceci autorise le compilateur a compilé l'expression au moment de la compilation.
===== Question 1 : =====
Tester le code suivant:
auto fn = estimate_function_time(factorial, 100);
std::cout << "Computing fact(100)=" << fn.second << " in " << fn.first.count() << " ticks.\n";
auto pw = estimate_function_time(power_by_int, 1.0002, 1000000);
std::cout << "Computing 1.02^100000=" << pw.second << " in " << pw.first.count() << " ticks.\n";
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 :
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, 1.0002, 100000); };
pw = estimate_function_time(power_10002_100000);
std::cout << "Computing 1.02^1000000=" << pw.second << " in " << pw.first.count() << " ticks.\n";
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 :
constexpr long double factorial(int n)
{
return n == 0 ? 1 : n * factorial(n - 1);
}
int main()
{
auto res = factorial(100);
std::cout << res << "\n";
}
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 :
int main()
{
auto res = factorial(100);
std::cout << res << "\n";
}
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.
Ainsi on force bien l'évaluation au moment de l'exécution.
Modifier le code des ''lambda'' expressions pour mettre en oeuvre ce mécanisme et estimer les temps de calculs.
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(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";
===== Question 4 : =====
Tester le code suivant avec un compilateur C++20:
template
struct constant_value
{
static constexpr long double constant() { constexpr auto result = power_by_int(value, power); return result; }
};
et appeller cette fonction comme suit:
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";
}
Expliquer ce qui se passe ? Et pourquoi ce résultat.
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.
template
struct precomputed_values
{
static long double pow() { constexpr auto result = power_by_int(value, 100000); return result; }
};
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.
====== Navigation ======
Partie 2: [[.part2|Mesurer le temps passé par une fonction]]