Pourquoi ne pas GCC optimiser un*un*un*un*un*un (a*a*a)*(a*a*a)?
Je fais un certain optimisation numérique sur une application scientifique. Une chose que j'ai remarqué, c'est que GCC permettra d'optimiser l'appel pow(a,2)
par la compilation en a*a
, mais l'appel pow(a,6)
n'est pas optimisé et fait appel une fonction de la bibliothèque pow
, ce qui a considérablement ralentit les performances. (En revanche, Le Compilateur Intel C++ , exécutable icc
, permettra d'éliminer l'appel de la bibliothèque pour pow(a,6)
.)
Ce que je suis curieux de savoir, c'est que quand j'ai remplacé pow(a,6)
avec a*a*a*a*a*a
à l'aide de GCC 4.5.1 et options "-O3 -lm -funroll-loops -msse4
", il utilise 5 mulsd
instructions:
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
alors que si j'écris (a*a*a)*(a*a*a)
, il va produire
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm13, %xmm13
qui réduit le nombre d'instructions de multiplication de 3. icc
a un comportement similaire.
Pourquoi les compilateurs ne pas reconnaître cette optimisation truc?
- Ce n' "reconnaissant pow(a,6)" signifie?
- Je suis surpris
gcc
ne pas optimiser ce. Les années 1970 compilateur FORTRAN j'ai utilisé sur CDC Cyber n'a ce genre de transformation, même sans sélectionner d'optimisation. Je pense que les Unix V6 (c. 1978)C
compilateur fait lorsque l'optimisation est activée, si de nombreuses optimisations il n'a été à enregistrer le code de l'espace, une denrée précieuse en ces jours. - Euh... vous savez que aaaaaa et (aaa)*(aa*a) ne sont pas la même chose avec des nombres à virgule flottante, n'est-ce pas? Vous aurez à utiliser -funsafe-math ou -ffast-math ou quelque chose pour que.
- Je vous suggère de lire "Ce que Chaque informaticien Devriez Savoir Sur l'Arithmétique à virgule Flottante" par David Goldberg: download.oracle.com/docs/cd/E19957-01/806-3568/... après quoi vous aurez une compréhension plus complète de la fonctionnalité tar pit que vous avez juste entré en!
- Une question parfaitement justifiée. 20 ans auparavant, j'ai posé la même question d'ordre général, et en écrasant que seul goulet d'étranglement, réduit le temps d'exécution d'une simulation de Monte Carlo à partir de 21 heures à 7 heures. Le code dans la boucle interne a été exécuté 13 milliards de fois dans le processus, mais il a obtenu la simulation en cours de nuit de la fenêtre. (voir la réponse ci-dessous)
- Peut-être jeter
(a*a)*(a*a)*(a*a)
dans le mélange, trop. Même nombre de multiplications, mais probablement plus exacte. - Pour commencer, comment cela est optimisé dépend grandement de ce type
a
a... - Shameless plug: en plus de Goldberg de papier, je suggère la lecture de la mine, hal.archives-ouvertes.fr/file/index/docid/281429/filename/...
- en fait, un bon optimiseur pourrait prendre un peu plus loin. a*a seulement besoin d'être effectuée qu'une fois. Les résultats pouvaient être réutilisés assez facilement à le réduire à seulement 3 opérations de multiplication.
- Oui, 3, exactement le même que
(a*a*a)*(a*a*a)
, c'est ce que j'ai proposé comme une alternative. Qu'essayez-vous de dire?
Vous devez vous connecter pour publier un commentaire.
Parce que Calcul en virgule flottante n'est pas Associatif. La façon dont vous groupe les opérandes en virgule flottante multiplication a un effet sur la précision numérique de la réponse.
En conséquence, la plupart des compilateurs sont très conservateurs au sujet de la réorganisation des calculs en virgule flottante, sauf s'ils peuvent être sûr que la réponse reste la même, ou à moins que vous leur dites que vous ne se soucient pas de l'exactitude numérique. Par exemple: l'option
-fassociative-math
de gcc qui permet de gcc pour réassocier les opérations en virgule flottante, ou même la-ffast-math
option qui permet encore plus agressif compromis de précision par rapport à la vitesse.pow
sont ni ici ni là; cette réponse n'a même pas de référencepow
.-fp-model precise
avec la CPI.clang
etgcc
défaut de conformité w.r.t. réassociation.-fassociative-math
serait inaccurrate; c'est juste quea*a*a*a*a*a
et(a*a*a)*(a*a*a)
sont différents. Ce n'est pas au sujet de l'exactitude; c'est à propos de conformité aux normes et strictement la répétabilité des résultats, p. ex. les mêmes résultats sur un compilateur. Les nombres à virgule flottante sont déjà pas exact. Il n'est inappropriée pour compiler avec-fassociative-math
.Lambdageek souligne à juste titre que, parce que l'associativité n'est pas valable pour les nombres à virgule flottante, le "optimisation" de
a*a*a*a*a*a
à(a*a*a)*(a*a*a)
peut modifier la valeur. C'est pourquoi il est rejeté par C99 (sauf si expressément autorisé par l'utilisateur, via le compilateur drapeau ou pragma). Généralement, l'hypothèse est que le programmeur a écrit ce qu'elle a fait pour une raison, et le compilateur doit la respecter. Si vous voulez(a*a*a)*(a*a*a)
, d'écrire cela.Qui peut être difficile à écrire, mais, pourquoi ne pouvons-le compilateur just do [ce que vous considérez être la chose lorsque vous utilisez
pow(a,6)
? Parce que ce serait l' mal chose à faire. Sur une plate-forme avec une bonne bibliothèque de mathématiques,pow(a,6)
est beaucoup plus précis que ce soita*a*a*a*a*a
ou(a*a*a)*(a*a*a)
. Simplement de fournir quelques données, j'ai couru une petite expérience sur mon Mac Pro, la mesure de la pire des erreurs dans l'évaluation d'un^6 pour tous flottante simple précision chiffres entre [1,2):À l'aide de
pow
au lieu d'une multiplication de l'arbre réduit l'erreur lié par un facteur de 4. Les compilateurs ne doit pas (et ne sont généralement pas) de faire des "optimisations" qui augmentent d'erreur sauf s'ils sont autorisés à le faire par l'utilisateur (par exemple, via-ffast-math
).Noter que GCC fournit
__builtin_powi(x,n)
comme une alternative àpow( )
, ce qui devrait générer une ligne de multiplication de l'arbre. L'utiliser si vous le souhaitez, faire des compromis précision pour les performances, mais ne souhaitez pas activer fast-math._set_SSE2_enable(<flag>)
avecflag=1
, il va utiliser le SSE2, si possible. Cela réduit la précision par un peu, mais améliore la vitesse (dans certains cas). MSDN: _set_SSE2_enable() et pow()pow
en utilisant uniquement des registres 32 bits, si la bibliothèque de l'écrivain est donc motivé. Il y a de l'ESS à base depow
des implémentations plus exact que la plupart des x87 implémentations basées, et il y a aussi des implémentations d'un compromis entre la précision de la vitesse.a*a*a*a*a*a
, mais ce n'est apparemment pas le cas! 🙂std::pow((long double)a,6)
. (c) Il existe une troisième voie: l'utilisation de double précision pour les calculs, par exemple l'appel à Szabolcs depower
modèle de fonction parpower<6,double>(a)
. Maintenant, vous obtenez une demi-ULP précision (comme unfloat
résultat), mais avec seulement une petite perte de performance (1,4 fois plus long quea*a*a*a*a*a
comme unfloat
). Comparer avec l'énorme performance de pénalité (32.4 fois plus de temps sur ma machine) que les résultats de l'appelstd::pow(float,float)
.(a*a)*(a*a)*(a*a)
dans le mélange, trop. Même nombre de multiplications, mais probablement plus exacte.Un autre cas semblable: la plupart des compilateurs n'optimise pas
a + b + c + d
à(a + b) + (c + d)
(c'est une optimisation depuis la seconde expression peut être canalisée mieux) et de l'évaluer en tant que donnée (c'est à dire que(((a + b) + c) + d)
). C'est aussi en raison de cas de coin:Ce sorties
1.000000e-05 0.000000e+00
Fortran (conçu pour le calcul scientifique) est doté d'un pouvoir d'opérateur, et pour autant que je sais compilateurs Fortran est généralement de l'optimiser sensibilisation pour les puissances entières d'une manière similaire à ce que vous décrivez. C/C++ malheureusement, n'ont pas une puissance de l'opérateur, seulement une fonction de la bibliothèque
pow()
. Ce qui n'empêche pas les compilateurs intelligentes de traitement depow
spécialement et de l'informatique dans un moyen plus rapide pour des cas particuliers, mais il semble qu'ils le font moins souvent ...Il y a quelques années j'ai essayé de le rendre plus pratique pour calculer les puissances entières d'une manière optimale, et est venu avec ce qui suit. C'est C++, C pas bien, et dépend encore le compilateur d'être un peu intelligent sur la façon d'optimiser/inline choses. De toute façon, j'espère que vous trouverez peut-être utile dans la pratique:
Précisions pour les curieux: ce n'est pas de trouver la solution optimale pour calculer les puissances, mais depuis trouver la solution optimale est un problème NP-complet de problème et c'est seulement la peine de le faire pour les petites puissances, de toute façon (par opposition à l'aide
pow
), il n'y a aucune raison de s'embêter avec les détails.Puis il suffit de l'utiliser comme
power<6>(a)
.Cela rend plus facile pour le type de pouvoirs (pas besoin de préciser 6
a
s avec les parenthèses), et vous permet d'avoir ce genre d'optimisation sans-ffast-math
dans le cas où vous avez quelque chose de précision dépendante comme compensée sommation (un exemple où l'ordre des opérations est indispensable).Vous pouvez probablement aussi oublier que c'est le C++ et l'utiliser dans le programme C (si on compile avec un compilateur C++).
J'espère que cela peut être utile.
EDIT:
C'est ce que je reçois de mon compilateur:
Pour
a*a*a*a*a*a
,Pour
(a*a*a)*(a*a*a)
,Pour
power<6>(a)
,GCC n'a réellement d'optimiser
a*a*a*a*a*a
à(a*a*a)*(a*a*a)
lorsque a est un entier. J'ai essayé avec cette commande:Il y a beaucoup de gcc drapeaux, mais rien de compliqué. Ils signifient: Lire depuis l'entrée standard stdin; utilisation O2 niveau d'optimisation; la sortie de l'assemblée la liste de langues au lieu d'une binaire; l'inscription doit utiliser Intel assemblée de la syntaxe du langage; l'entrée est en langage C (généralement de langue est déduite à partir de l'entrée de l'extension de fichier, mais il n'y a pas d'extension de fichier lors de la lecture de l'entrée standard stdin); et d'écrire sur la sortie standard stdout.
Voici la partie importante de la production. J'ai annoté avec quelques commentaires en indiquant ce qui se passe dans la langue de l'assembly:
Je suis en utilisant le système de GCC sous Linux Mint 16 Petra, un dérivé d'Ubuntu. Voici la version de gcc:
Que d'autres affiches ont noté, cette option n'est pas possible en virgule flottante, parce que l'arithmétique à virgule flottante n'est pas associatif.
unsigned int
, trop.Parce qu'un 32 bits à virgule flottante nombre - comme 1.024 - n'est pas 1.024. Dans un ordinateur, 1.024 est un intervalle: de (1.024-e) (1.024+e), où "e" représente une erreur. Certaines personnes ne parviennent pas à réaliser que cela et croire aussi que * dans*un est synonyme de multiplication des nombres en précision arbitraire, sans qu'il y ait des erreurs liées à ces numéros. La raison pour laquelle certaines personnes ne parviennent pas à réaliser que cela est peut-être le calcul les calculs qu'ils ont exercé dans les écoles élémentaires: le fait de travailler uniquement avec l'idéal numéros sans erreurs attachées, et de croire que c'est OK pour ignorer simplement "e" lors de l'exécution de la multiplication. Ils ne voient pas le "e" est implicite dans "float a=1.2", "un*un*un" semblable à l'codes C.
Devrait majorité des programmeurs reconnaître (et être capable de s'exécuter sur) l'idée que C une expression un*un*un*un*un*un n'est pas réellement de travail idéal avec des chiffres, le compilateur GCC serait alors LIBRE d'optimiser "un*un*un*un*un*un" à dire "t=(a*a); t*t*t", ce qui nécessite un plus petit nombre de multiplications. Mais malheureusement, le compilateur GCC ne sais pas si le programmeur écrit le code, pense que "a" est un nombre avec ou sans erreur. Et donc, GCC ne feront que le code source ressemble - parce que c'est ce que GCC voit avec son "œil nu".
... une fois que vous savez quel genre de programmeur vous sont, vous pouvez utiliser le bouton "-ffast-math" commutateur de dire à GCC "Hey, GCC, je sais ce que je fais!". Cela permettra de GCC pour convertir un*un*un*un*un*un dans un autre morceau de texte, il semble différent d'un*un*un*un*un*un - mais encore calcule un nombre de l'intervalle d'erreur d'un*un*un*un*un*un. C'est OK, puisque vous savez déjà que vous travaillez avec des intervalles, pas idéal numéros.
int x = 3
en ce sens quex
est 3+/-0.5.Distance = Math.Sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1))
, le but deDistance
est de représenter la distance Euclidienne entre (x1,y1,z1) et (x2,y2,z2). Il est peu probable que le nombre précis stockées dansDistance
sera la précision de la distance Euclidienne entre deux points, mais...Distance
n'est pas exactement égale à sa valeur numérique; il signifie que la valeur numérique n'est qu'une approximation certaine quantité physique modélisé.Distance
représente cette valeur, ou peut-êtreDistance
représente quelque chose qui est à toutes fins pratiques "assez proche" de la valeur, plutôt que d'indiquer explicitement queDistance
représente la précision en virgule flottante valeur numérique qui aurait des résultats de l'exécution de ladite séquence d'opérations. Du point de vue du matériel, effectuer les calculs de primitives (multiplie, ajoute, sqrt, etc.) les quantités doivent être évalués exactement, mais pour le consommateur, ils représentent des approximations.someSingle = 1.0/10.0
, le résultat sera d'autant plus précise que le consommateur va attendre; si le code effectuesomeDouble = 1.0f/10.0f
, le résultat va être désactivé par de nombreux ordres de grandeur de plus que les consommateurs qui ont connu lefloat
quantités qui s'est passé pour représenter les valeurs précises qui serait de nature à attendre. Si unDouble
est jeté à l'Float
et jamais jeté en arrière, l'utilisateur aura pas de surprises au niveau de la précision. Les Conversions deFloat
àDouble
, cependant, sont beaucoup plus susceptibles d'avoir des "surprises".Pas d'affiches ont mentionné la contraction des expressions flottantes encore (ISO standard C, 6.5p8 et 7.12.2). Si le
FP_CONTRACT
pragma est fixé àON
, le compilateur est autorisé à l'égard d'une expression telle quea*a*a*a*a*a
comme une seule opération, comme si on l'évalue exactement avec un seul arrondissement. Par exemple, un compilateur peut le remplacer par un interne en fonction de la puissance qui est à la fois plus rapide et plus précis. Ceci est particulièrement intéressant que le comportement est en partie contrôlé par le programmeur directement dans le code source, tandis que les options du compilateur fourni par l'utilisateur final peut parfois être utilisé de manière incorrecte.L'état par défaut de la
FP_CONTRACT
pragma la mise en œuvre est définie, de sorte qu'un compilateur est autorisé à faire de telles optimisations par défaut. Ainsi, portable code qui doit suivre strictement la norme IEEE 754 règles devraient explicitement définie àOFF
.Si un compilateur ne supporte pas cette pragma, il doit être prudent en évitant toute optimisation, dans le cas où le promoteur a choisi de la mettre à l'
OFF
.GCC ne prend pas en charge cette pragma, mais avec les options par défaut, on suppose qu'il est
ON
; ainsi, pour les cibles avec un matériel FMA, si l'on veut empêcher la transformationa*b+c
de fma(a,b,c), on a besoin de fournir une option comme-ffp-contract=off
(à définir explicitement le pragma pourOFF
) ou-std=c99
(à demander à GCC de se conformer à certaines C version standard, ici C99, donc suivez le paragraphe ci-dessus). Dans le passé, cette dernière option n'était pas la prévention de la transformation, ce qui signifie que GCC n'était pas conforme sur ce point: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845Comme Lambdageek souligné flotteur de la multiplication n'est pas associatif et vous pouvez obtenir moins de précision, mais aussi lorsqu'obtenir une meilleure précision, vous pouvez faire valoir à l'encontre de l'optimisation, parce que vous voulez une déterministe de l'application. Par exemple, dans le jeu de simulation de client/serveur, où chaque client a pour simuler le même monde que vous voulez les calculs en virgule flottante d'être déterministe.
Je n'aurais pas prévu ce cas être optimisé à tous. Il ne peut pas être très souvent lorsqu'une expression contient des sous-expressions qui peuvent être regroupés pour supprimer toutes les activités. Je m'attends à ce compilateur écrivains à investir de leur temps dans des zones qui seraient plus susceptibles d'entraîner des améliorations notables, plutôt que de couvrir un rarement rencontré de cas de bord.
J'ai été surpris d'apprendre de l'autre des réponses que cette expression pourrait en effet être optimisé avec le bon compilateur commutateurs. Soit l'optimisation est trivial, ou c'est un cas limite de un beaucoup plus commun de l'optimisation, ou le compilateur écrivains ont été extrêmement complet.
Il n'y a rien de mal à donner des trucs pour le compilateur comme vous l'avez fait ici. C'est une étape normale et attendue de la part de la micro-optimisation des processus de réorganiser les états et les expressions pour voir quelles différences ils apportent.
Tandis que le compilateur peut être justifiée en considérant les deux expressions de livrer des résultats incohérents (sans les commutateurs appropriés), il n'y a pas besoin pour vous d'être lié par cette restriction. La différence sera incroyablement minuscule - de sorte que si la différence de vous, vous ne devriez pas utiliser la norme arithmétique à virgule flottante en premier lieu.
Des fonctions de la bibliothèque comme "prisonnier de guerre" sont généralement soigneusement conçus pour donner le minimum d'erreur possible (dans le cas générique). Ceci est habituellement réalisé l'approximation des fonctions splines (d'après Pascal le commentaire le plus fréquent de la mise en œuvre semble être l'utilisation d' L'algorithme de Remez)
fondamentalement l'opération suivante:
a une erreur inhérente à environ la même ordre de grandeur que l'erreur dans une seule multiplication ou de la division.
Alors que l'opération suivante:
a une erreur inhérente qui est supérieure de plus de 5 fois l'erreur d'une seule multiplication ou de la division (parce que vous êtes la combinaison de 5 multiplications).
Le compilateur doit être vraiment attention à la nature de l'optimisation, il est en train de faire:
pow(a,6)
àa*a*a*a*a*a
il peut améliorer les performances, mais de réduire considérablement la précision des nombres à virgule flottante.a*a*a*a*a*a
àpow(a,6)
il peut effectivement réduire la précision, parce que "a" est une valeur spéciale qui permet la multiplication sans erreur (une puissance de 2 ou certaines petit nombre entier)pow(a,6)
à(a*a*a)*(a*a*a)
ou(a*a)*(a*a)*(a*a)
il y a encore peut être une perte de précision par rapport àpow
fonction.En général, vous savez que pour arbitraire de valeurs à virgule flottante de "prisonnier de guerre" a une meilleure précision que n'importe quelle fonction vous pourrait éventuellement écrire, mais dans certains cas, plusieurs multiplications pouvez avoir plus de précision et de performance, c'est au développeur de choisir ce qui est le plus approprié, éventuellement commenter le code, de sorte que personne d'autre n'aurait "optimiser" le code.
La seule chose qui a du sens (opinion personnelle, et, apparemment, le choix du CCAG wichout tout particulier de l'optimisation ou le compilateur drapeau) pour optimiser devrait remplacer "pow(a,2)" avec une "*un". Ce serait la seule saine d'esprit, chose un fournisseur de compilateur doit faire.
Il y a déjà un peu de bonnes réponses à cette question, mais par souci d'exhaustivité, je tenais à préciser que l'article applicable de la norme C est 5.1.2.2.3/15 (qui est le même que l'article 1.9/9 (C++11). Cette section précise que les opérateurs ne peuvent être regroupés que si elles sont vraiment associatif ou commutative.
gcc pouvez faire cette optimisation, même pour les nombres à virgule flottante. Par exemple,
devient
avec
-O -funsafe-math-optimizations
. Cette réorganisation viole la norme IEEE-754, cependant, il exige que le drapeau.Entiers signés, comme Peter Cordes souligné dans un commentaire, peut faire cette optimisation sans
-funsafe-math-optimizations
car il détient exactement quand il n'y a pas de débordement et si il y a débordement de vous obtenir un comportement indéfini. Ainsi, vous obtenezavec juste
-O
. Pour des entiers non signés, il est encore plus facile puisqu'ils travaillent mod des puissances de 2, et donc peuvent être réorganisés librement, même dans le visage de débordement.-ffast-math
)