User Tools

Site Tools


in204:tds:sujets:td12_2023:part3

Partie 3 : Exécution à la compilation

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<long double>, 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<class numericalT>
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<long double>, 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<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";

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.

Correction

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";

Question 4 :

Tester le code suivant avec un compilateur C++20:

template<double value, int power>
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.

Correction

Correction

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<double value, int power>
struct precomputed_values
{
	static long double pow() { constexpr auto result = power_by_int<long double>(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

in204/tds/sujets/td12_2023/part3.txt · Last modified: 2023/12/04 11:24 by bmonsuez