Quand dois-je utiliser des modèles d'expressions C ++ en science informatique et quand ne dois-je * pas * les utiliser?

24

Supposons que je travaille sur un code scientifique en C ++. Lors d'une récente discussion avec un collègue, il a été avancé que les modèles d'expression pouvaient être une très mauvaise chose, rendant potentiellement le logiciel compilable uniquement sur certaines versions de gcc. Soi-disant, ce problème a affecté quelques codes scientifiques, comme mentionné dans les sous-titres de cette parodie de Downfall . (Ce sont les seuls exemples que je connaisse, d'où le lien.)

Cependant, d'autres personnes ont fait valoir que les modèles d'expression sont utiles car ils peuvent générer des gains de performances, comme dans cet article du SIAM Journal of Scientific Computing , en évitant le stockage des résultats intermédiaires dans des variables temporaires.

Je ne sais pas grand-chose sur la métaprogrammation de modèles en C ++, mais je sais que c'est une approche utilisée dans la différenciation automatique et dans l'arithmétique d'intervalle, c'est ainsi que je suis entré dans une discussion sur les modèles d'expression. Étant donné à la fois les avantages potentiels des performances et les inconvénients potentiels de la maintenance (si c'est le bon mot), quand dois-je utiliser des modèles d'expression C ++ en science informatique et quand dois-je les éviter?

Geoff Oxberry
la source
Ah, la vidéo est trop drôle. Je ne savais pas que ça existait. Qui l'a fait, tu sais?
Wolfgang Bangerth
Aucune idée; quelques personnes du PETSc m'ont envoyé des liens à un moment donné. Je pense qu'un développeur FEniCS l'a fait.
Geoff Oxberry
Le lien vidéo est rompu et je meurs de curiosité. Nouveau lien?
Praxeolitic
Oh drat, peu importe, je vois que YouTube est venu pour nos vidéos Hitler.
Praxeolitic

Réponses:

17

Mon problème avec les modèles d'expression est qu'ils sont une abstraction très fuite. Vous passez beaucoup de travail à écrire du code très compliqué pour effectuer une tâche simple avec une syntaxe plus agréable. Mais si vous voulez changer l'algorithme, vous devez jouer avec le code sale et si vous vous trompez avec les types ou la syntaxe, vous obtenez des messages d'erreur complètement inintelligibles. Si votre application correspond parfaitement à une bibliothèque basée sur des modèles d'expression, cela peut valoir la peine d'être considéré, mais si vous n'êtes pas sûr, je recommanderais simplement d'écrire du code normal. Bien sûr, le code de haut niveau est moins joli, mais vous pouvez simplement faire ce qui doit être fait. En tant qu'avantage, le temps de compilation et les tailles binaires diminueront considérablement et vous n'aurez pas à faire face à d'énormes variations de performances en raison du choix du compilateur et de l'indicateur de compilation.

Jed Brown
la source
Oui, j'ai vu certains des longs messages d'erreur de première main quand j'ai dû porter du code de gcc 2.95 à gcc 4.x, et le compilateur lançait toutes sortes d'erreurs sur les modèles. Un de mes collègues de laboratoire développe une bibliothèque de modèles pour l'arithmétique d'intervalle en C ++ (en ajoutant de nouvelles fonctionnalités qui ne sont pas dans Boost :: Interval afin d'accomplir plus de recherches), et je ne veux pas voir le code devenir un cauchemar compiler.
Geoff Oxberry
12

D'autres ont commenté la difficulté d'écrire des programmes ET ainsi que la complexité de la compréhension des messages d'erreur. Permettez-moi de commenter la question des compilateurs: il est vrai qu'il y a quelque temps, l'un des gros problèmes était de trouver un compilateur suffisamment conforme à la norme C ++ pour que tout fonctionne et qu'il fonctionne de manière portable. En conséquence, nous avons trouvé beaucoup de bogues - j'ai 2 à 300 rapports de bogues en mon nom, distribués sur gcc, Intel icc, IBM xlC et pgicc de Portland. Par conséquent, le script de configuration deal.II est un référentiel d'un grand nombre de tests de bogues du compilateur, principalement dans le domaine des modèles, des déclarations d'amis, des espaces de noms, etc.

Mais, il s'avère que les fabricants de compilateurs se sont vraiment mis d'accord: aujourd'hui, gcc et icc passent tous nos tests aujourd'hui et il est facile d'écrire du code portable entre les deux. Je dirais que l'IGP n'est pas loin derrière, mais il a un certain nombre de bizarreries qui ne semblent pas disparaître au fil des ans. xlC, d'autre part, est une toute autre histoire - ils corrigent un bogue tous les 6 mois, mais malgré le dépôt de rapports de bogues avec eux pendant des années, les progrès sont extrêmement lents et xlC n'a jamais été en mesure de compiler deal.II avec succès.

Ce que tout cela signifie, c'est ceci: si vous vous en tenez aux deux gros compilateurs, vous pouvez vous attendre à ce qu'ils fonctionnent juste aujourd'hui. Étant donné que la plupart des ordinateurs et des systèmes d'exploitation en ont aujourd'hui au moins un, cela suffit. La seule plate-forme où les choses sont plus difficiles est le BlueGene, où le compilateur système est généralement xlC, avec tous ses bogues.

Wolfgang Bangerth
la source
Par curiosité, avez-vous essayé de compiler avec les nouveaux compilateurs xlc sur / Q?
Aron Ahmadia
Non, je dois admettre que j'ai abandonné le xlC.
Wolfgang Bangerth
5

J'ai expérimenté un peu avec ET il y a longtemps quand, comme vous l'avez mentionné, les compilateurs étaient toujours en difficulté avec eux. J'ai utilisé la bibliothèque Blitz pour l'algèbre linéaire dans un de mes codes. Le problème était alors d'obtenir le bon compilateur et comme je ne suis pas un programmeur C ++ parfait, d'interpréter les messages d'erreur du compilateur. Ce dernier était tout simplement ingérable. Le compilateur générerait en moyenne environ 1 000 lignes de messages d'erreur. Pas moyen que j'ai pu trouver rapidement mon erreur de programmation.

Vous pouvez trouver plus d'informations sur la page Web oonumerics (il y a les actes de deux ateliers ET).

Mais je resterais loin d'eux ...

GertVdE
la source
Les messages d'erreur du compilateur sont en effet l'une de mes préoccupations. Avec certains des modèles de code C ++ que je compile afin de créer des bibliothèques pour mes projets, le compilateur peut générer des centaines de lignes de messages d'avertissement. Cependant, ce n'est pas mon code, je ne le comprends pas, et de manière générale, cela fonctionne, alors je le laisse tranquille. Les longs messages d'erreur cryptiques n'augurent rien de bon pour le débogage.
Geoff Oxberry
4

Le problème commence déjà avec le terme «modèles d'expression (ET)». Je ne sais pas s'il existe une définition précise. Mais dans son utilisation courante, il associe en quelque sorte «comment vous codez des expressions d'algèbre linéaire» et «comment il est calculé». Par exemple:

Vous codez l'opération vectorielle

v = 2*x + 3*y + 4*z;                    // (1)

Et il est calculé par une boucle

for (int i=0; i<n; ++i)                 // (2)
    v(i) = 2*x(i) + 3*y(i) + 4*z(i);

À mon avis, ce sont deux choses différentes et doivent être découplées: (1) est une interface et (2) une implémentation possible. Je veux dire que c'est une pratique courante en programmation. Bien sûr, (2) peut être une bonne implémentation par défaut, mais en général, je veux pouvoir utiliser une implémentation spécialisée et dédiée. Par exemple, je veux qu'une fonction comme

myGreatVecSum(alpha, x, beta, y, gamma, z, result);    // (3)

être appelé quand je suis en train de coder (1). Peut-être que (3) utilise simplement en interne une boucle comme dans (2). Mais en fonction de la taille du vecteur, d'autres implémentations pourraient être plus efficaces. Quoi qu'il en soit, un expert en hautes performances peut implémenter et régler (3) autant que possible. Donc, si (1) ne peut pas être mappé à un appel de (3), j'évite plutôt le sucre syntaxique de (1) et j'appelle directement (3) tout de suite.

Ce que je décris n’a rien de nouveau. Au contraire, c'est l'idée derrière BLAS / LPACK:

  • Toutes les opérations critiques de performances dans LAPACK sont effectuées en appelant les fonctions BLAS.
  • BLAS définit simplement une interface pour les expressions d'algèbre linéaire qui sont couramment nécessaires.
  • Pour BLAS, différentes implémentations optimisées existent.

Si la portée de BLAS n'est pas suffisante (par exemple, elle ne fournit pas une fonction comme (3)), alors on peut étendre la portée de BLAS. Ce dinosaure des années 60 et 70 réalise donc avec son outil de l'âge de pierre une séparation nette et orthogonale de l'interface et de la mise en œuvre. C'est assez drôle que (la plupart) des bibliothèques numériques C ++ n'atteignent pas ce niveau de qualité logicielle. Bien que le langage de programmation lui-même soit beaucoup plus sophistiqué. Il n'est donc pas surprenant que BLAS / LAPACK soit toujours en vie et activement développé.

Donc, à mon avis, les ET ne sont pas mauvais en soi. Mais la façon dont ils sont couramment utilisés dans les bibliothèques numériques C ++ leur a valu une très mauvaise réputation dans les cercles de calcul scientifique.

Michael Lehn
la source
Michael, je pense que vous manquez l'un des points des modèles d'expression. Votre exemple de code (1) ne correspond en fait à aucun appel BLAS optimisé. En fait, même quand une routine BLAS existe, la surcharge d'un appel de fonction BLAS la rend assez terrible pour les petits vecteurs et matrices. Les bibliothèques de modèles d'expression sophistiquées comme Blaze et Eigen peuvent utiliser une évaluation d'expression différée pour éviter l'utilisation de temporaires, mais je suis convaincu que presque rien de moins qu'un langage spécifique à un domaine va pouvoir battre l'algèbre linéaire roulée à la main.
Aron Ahmadia
Non, je pense que vous manquez le point. Il faut distinguer entre (a) BLAS comme une spécification de certains souvent nécessaire algèbre linéaire opération (b) une mise en œuvre de BLAS comme ATLAS, GotoBLAS, etc. BTW que la façon dont cela fonctionne dans FLENS: Par défaut , une expression comme (1) serait être évalué en appelant axpy de BLAS trois fois. Mais sans modifier (1) je pourrais aussi l'évaluer comme dans (2). Donc, ce qui se passe logiquement est le suivant: si une opération comme dans (1) est importante, alors l'ensemble des opérations BLAS spécifiées (a) peut être étendu.
Michael Lehn
Donc, le point clé est: la notation comme «v = x + y + z» et la façon dont elle est finalement calculée doivent être séparées. Eigen, MTL, BLITZ, blaze-lib échouent complètement à cet égard.
Michael Lehn
1
C'est vrai, mais le nombre d'opérations d'algèbre linéaire fréquemment nécessaires est combinatoire. Si vous allez utiliser un langage comme C ++, vous avez le choix entre l'implémentation selon les besoins en utilisant des modèles d'expression (c'est l'approche Eigen / Blaze) en combinant intelligemment des sous-blocs et des algorithmes en utilisant une évaluation différée, ou en implémentant un massif bibliothèque de toutes les routines possibles. Je ne préconise aucune de ces approches, car des travaux récents dans Numba et Cython montrent que nous pouvons obtenir des performances similaires ou meilleures en travaillant à partir de langages de script de haut niveau comme Python.
Aron Ahmadia
Mais encore une fois, ce dont je me plains, c'est le fait que des bibliothèques aussi sophistiquées (dans le sens de complexes mais inflexibles) comme Eigen couplent étroitement la notation et le mécanisme d'évaluation et pensent même que c'est une bonne chose. Si j'utilise un outil comme Matlab, je veux juste coder les choses et compter que Matlab fait la meilleure chose possible. Si j'utilise un langage comme C ++, je veux garder le contrôle. Alors appréciez s'il existe un mécanisme d'évaluation par défaut mais il doit être possible de le changer. Sinon, je reviens en arrière et j'appelle directement les fonctions en C ++.
Michael Lehn