Bonsoir,
Cela fait quelques temps que je travaille sur des simulations numériques utilisant à outrance des nombres complexes. Pour ce faire, j'utilise la classe std::complex<> de la SL bien qu'à mon goût elle ne soit pas optimale dans son design.
Dans de nombreux calculs, je multiplie des nombres réels ou complexes par l'unité imaginaire i (sqrt(-1)). Le moyen le plus simple de faire cela est de définir une constante complexe valant i définie comme suit:
const std::complex<double> ii(0.,1.)
et d'utiliser cette constante là où une multiplication par i est nécessaire.
Cependant, ceci n'est pas du tout optimal puisque étant donné le type même de cette variable, le calcul du produit de i et d'un autre nombre complexe est inutilement coûteux. Alors que i*(a+ib) s'obtient facilement en deux opérations, la manière dont la classe std::complex<> est implémentée force le calcul de (0+i)*(a+ib) ce qui est nettement plus coûteux.
J'ai cherché à améliorer cela dans mes calculs. La première manière d'y arriver est de récrire toutes mes équations séparément pour la partie réelle et la partie imaginaire, mais ce n'est pas très pratique et je préférais une solution plus "informatique". J'ai donc défini une classe Imaginary<> représentant un nombre purement imaginaire (i.e. a*i) et proposant toutes les opérations que std::complex<> offre. J'ai alors pu observer un gain assez spectaculaire dans mes simulations.
Je vous présente donc mon code et je viens vous demander votre avis sur la manière dont ma classe et ses fonctions externes sont implémentées.
L'utilisation de la classe est très simple. Pour l'unité imaginaire, on déclare simplement
const Imaginary<double> ii(1.)
et tous les opérateurs réels - imaginaires et complexe - imaginaire sont implémentés. Vous pouvez trouver le code de cette classe en pièce jointe.
L'exemple fournit avec est un cas typique d'utilisation. Il s'agit d'un code faisant évoluer une fonction d'onde dans un potentiel au moyen de l'équation de Schroedinger.
En compilant avec g++ 4.6.1 sur mon core i7-2820QM et avec les options -O2 ou -O3, j'obtiens un temps d'exécution de la partie calculatoire de 11.85 secondes.
Si j'utilise l'option -DUSE_IMAGINARY qui active l'utilisation de ma classe pour la variable ii au lieu de std::complex<>, j'obtiens un temps de
4.84 secondes pour la même portion de code, soit un gain de 2.4 !
J'obtiens les mêmes proportions si je fais itérer le code un plus grand nombre de fois pour éliminer les fluctuations.
Le compilateur Intel me fournit approximativement les mêmes valeurs.
Le code teste explicitement le gain dû à des multiplications par i, mais il y a aussi des gains possibles pour les opérations de division ou pour toutes les fonctions trigonométriques et hyperboliques par exemple, bien que les cas d'utilisation soient, à mon sens, plus rares.
Que pensez-vous de cette classe ? Pourriez-vous tester ce morceau de code chez vous et reporter quelques valeurs ?
Merci d'avance pour votre aide,
Nanoc
Partager