Salut à tous !
Me voici, petit nouveau sur ce forum. Pour mon premier message, j'ai une question assez technique. Laissez-moi tout d'abord vous expliquer un peu le contexte : je participe au développement d'un nouveau modèle océanographique. Il se base sur un projet plus ancien, développé par des physiciens qui ont découvert la programmation sur le tas, d'abord en Fortran puis en C. Maintenant, le code migre vers du C++. Je vous laisse imaginer le mélange... Le plus étonnant, c'est qu'il est plutôt performant, notamment en comparaison des modèles déjà existant.
Dans certains cas, un calcul peut-être effectué de plusieurs manières, en fonction de la précision désirée. Pour pouvoir faire le choix en cours d'exécution, la procédure de calcul est envoyé sous forme de pointeur de fonction. Le problème, c'est qu'alors le polymorphisme est délicat et que les performances peuvent être médiocres, surtout en comparaison des foncteurs. Je propose donc de passer à cette dernière solution. Avant de changer le code, il m'a été demandé de faire un petit exemple et du profilage, d'abord juste avec la bibliothèque standard, ensuite avec Boost.
Et de m'exécuter.
Cependant, j'ai un petit problème. Soit le code suivant --- il compile, mais ne fait rien, j'essaye juste d'isoler mon problème et de montrer la hiérarchie que j'ai mise en place sans plus noyer le poisson, c'est déjà assez long comme cela :
Par contre, si je change la définition de ma classe FonctionSimple de la sorte :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 /** * \file main.cpp * \brief Exemple d'utilisation des foncteurs en n'utilisant que la * bibliothèque standard. * \author Yoann LE BARS * \date 22 avril 2008 * \version 1.0 */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <iostream> #include <functional> #include <cmath> #include <cstdlib> #include <vector> /** * \namespace Fonction * * Espace de nommage contenant des foncteurs. */ namespace Fonction { /** * \class FonctionSimple * \brief Class de base pour les fonctions d'une variable. */ template <class Arg, class Res> class FonctionSimple: public std::unary_function<Arg, Res> { public: /** * \brief Surcharge de l'operateur parenthèse. * * Fonction purement virtuelle. */ Res operator () (const Arg &); }; // class FonctionSimple /** * \class Normale * \brief Implementation sous forme de foncteur de la loi normale. */ template <class T> class Normale: public FonctionSimple<T, T> { private: /// Espérance T mu; /// Écart type T sigma; public: /** * \brief Constructeur par défaut. * \param mu_ Initialiseur de l'espérance. * \param sigma_ Initialiseur de l'écart type. * * Si aucune valeur n'est donnée, initialisation à zéro. */ Normale (const T &mu_ = static_cast<T>(0), const T &sigma_ = static_cast<T>(0)): mu (mu_), sigma (sigma_) {} // Gaussienne (const T &, const T &) /** * \brief Donne l'espérance. * \return mu * * La modification n'est pas possible. */ T esperance (void) const {return mu;} /** * \brief Donne l'écart type. * \return sigma * * La modification n'est pas possible. */ T ecartType (void) const {return sigma;} /** * \brief Donne l'espérance. * \return mu * * La modification est possible. */ T &esperance (void) {return mu;} /** * \brief Donne l'écart type. * \return sigma * * La modification est possible. */ T &ecartType (void) {return sigma;} /** * \brief Surcharge de l'opérateur parenthèse. * \param x Valeur dont on veut connaître la probabilité. */ T operator () (const T &x) const { const T facteur = static_cast<T>(1) / (sigma * static_cast<T>(std::sqrt(2. * M_PI))); const T numerateur = x - mu; return facteur * std::exp(-(numerateur * numerateur) / (static_cast<T>(2) * sigma * sigma)); } // T operator () (const Arg &) const }; // class Normale } // namespace Fonction /** * \namespace Utile * * Espace de nommage comprenant des fonctions utilitaires. */ namespace Utile { /** * \brief Tire une valeur aléatoire au sort. * \param min Valeur minimum. * \param max Valeur maximum. * \return Une valeur aléaatoire dans [min ; max]. */ template <class T> inline T aleatoire (const T &min, const T &max) throw () { return static_cast<T>(std::rand()) / static_cast<T>(RAND_MAX) * (max - min) + min; } // template <class T> T aleatoire (const T &, const T &) } // namespace Utile /** * \brief Corps du programme. * \param argc Nombre d'arguments dans la ligne de commande. * \param argv La ligne de commande. * \return 0 si tout va bien, -1 si une quelconque exception est déclanchée. */ int main(int argc, char **argv) { try { std::srand(static_cast<unsigned int>(std::time(NULL))); const double mu = Utile::aleatoire(0., 100.); const double sigma = Utile::aleatoire(0., 100.); const Fonction::Normale<double> f (mu, sigma); return 0; } catch (const std::exception &e) { std::cerr << "Exception caught: " << e.what() << std::endl; return -1; } } // main
Alors j'obtiens l'erreur suivante à l'édition des liens (et non pas à la compilation) :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 /** * \class FonctionSimple * \brief Class de base pour les fonctions d'une variable. */ template <class Arg, class Res> class FonctionSimple: public std::unary_function<Arg, Res> { public: /** * \brief Surcharge de l'operateur parenthèse. * * Fonction purement virtuelle. */ virtual Res operator () (const Arg &); }; // class FonctionSimple
Sachant que mon idée est d'en arriver finalement à quelque chose de ce genre :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 Building CXX object CMakeFiles/exemplefoncteur.dir/main.o /usr/bin/c++ -ansi -pedantic -Wall -g -o CMakeFiles/exemplefoncteur.dir/main.o -c /home/lebars/test/exemplefoncteur/main.cpp Linking CXX executable exemplefoncteur /usr/bin/cmake -P CMakeFiles/exemplefoncteur.dir/cmake_clean_target.cmake /usr/bin/c++ -fPIC -L /home/softs/fftpack/lib/ -L /home/softs/fftpack/lib/ "CMakeFiles/exemplefoncteur.dir/main.o" -o exemplefoncteur -rdynamic CMakeFiles/exemplefoncteur.dir/main.o:(.rodata._ZTVN8Fonction7NormaleIdEE[vtable for Fonction::Normale<double>]+0x10): undefined reference to `Fonction::FonctionSimple<double, double>::operator()(double const&)' CMakeFiles/exemplefoncteur.dir/main.o:(.rodata._ZTVN8Fonction14FonctionSimpleIddEE[vtable for Fonction::FonctionSimple<double, double>]+0x10): undefined reference to `Fonction::FonctionSimple<double, double>::operator()(double const&)' collect2: ld returned 1 exit status
Pour pouvoir ensuite faire ce genre de choses :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 /** * \class FonctionSimple * \brief Class de base pour les fonctions d'une variable. */ template <class Arg, class Res> class FonctionSimple: public std::unary_function<Arg, Res> { public: /** * \brief Surcharge de l'operateur parenthèse. * * Fonction purement virtuelle. */ virtual Res operator () (const Arg &) = 0; }; // class FonctionSimple
Je dois admettre que je ne comprends pas pourquoi cette erreur apparaît, ni surtout pourquoi elle a lieu à l'édition des liens. D'après Luc Hermitte (il me semble qu'il intervient également sur ce forum) :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 /** * \brief Appelle une fonction sur un vecteur de valeurs et écrit les * résultats sur la sortie standard. * \param f La fonction. * \param x Le vecteurs des valeurs. */ template <class Arg, class Res> void test (const Fonction::FonctionSimple<Arg, Res> &f, const std::vector<Arg> &x) { for (size_t i = 0; i < x.size(); ++i) std::cout << f(x[i]) << std::endl; } /* template <class Arg, class Res> void test (const std::unary_function<Arg, Res> &, const std::vector<Arg> &) */
Ceci explique peut-être cela, si quelqu'un a une idée, je suis preneur.Envoyé par Luc Hermitte
D'autre part, Luc Hermitte (toujours lui), m'a également fait cette remarque :
Remarque pertinente, quoique j'aimerais en savoir un peu plus à ce sujet. De plus, s'agissant d'un code de calcul numérique assez intense, les performances sont tout de même relativement importantes, donc il serait bon que le développement en ligne ait lieu. Si des gens ont des précisions à ce sujet et pas seulement dans le cas particulier de GCC (j'essaye de faire en sorte que le code soit le plus portable possible), je lui en saurais gré.Attention aux foncteurs par référence. Le fait qu'ils soient passés par valeur n'est pas innocent à mon avis. C'est une des règles conseillée dans Effective STL de Scott Meyers. En diagonale, je lis que la SL prend par valeur pour pouvoir accepter pointeur de fonction comme foncteur sans broncher. Puis toute une discussion sur le slicing qui attend au tournant et que donc pour avoir du polymorphisme, il faut introduire une indirection à la pseudo-pimpl, vu que le type d'implémentation sera celui dérivé.
Après, je ne sais pas si cela a des impacts sur la capacité du compilo à inliner l'appel. Il faudrait faire des tests et décoder l'assembleur généré.
À bientôt.
Le Farfadet Spatial
Partager