Pourquoi Clang optimise-t-il la boucle dans ce code
#include <time.h>
#include <stdio.h>
static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };
int main()
{
clock_t const start = clock();
for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}
mais pas la boucle dans ce code?
#include <time.h>
#include <stdio.h>
static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };
int main()
{
clock_t const start = clock();
for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}
(Balisage à la fois en C et C ++ car j'aimerais savoir si la réponse est différente pour chacun.)
c++
c
optimization
floating-point
clang
user541686
la source
la source
-O3
, je ne sais pas comment vérifier ce que cela active.static double arr[N]
n'est pas autorisé en C;const
les variables ne comptent pas comme des expressions constantes dans cette langueRéponses:
La norme IEEE 754-2008 pour l'arithmétique à virgule flottante et la norme ISO / CEI 10967 pour l'arithmétique indépendante du langage (LIA), partie 1 expliquent pourquoi il en est ainsi.
Le cas de l'addition
Sous le mode d'arrondi par défaut (Round-to-Nearest, Ties-to-Even) , nous voyons que
x+0.0
produitx
, SAUF quandx
est-0.0
: Dans ce cas, nous avons une somme de deux opérandes avec des signes opposés dont la somme est zéro, et §6.3 paragraphe 3 règles que cet ajout produit+0.0
.Comme il
+0.0
n'est pas identique au niveau du bit à l'original-0.0
, et qu'il-0.0
s'agit d'une valeur légitime qui peut apparaître en entrée, le compilateur est obligé de mettre le code qui transformera les zéros négatifs potentiels en+0.0
.Le résumé: Dans le mode d'arrondi par défaut, dans
x+0.0
, six
-0.0
, alorsx
elle-même est une valeur de sortie acceptable.-0.0
, alors la valeur de sortie doit être+0.0
, qui n'est pas identique au niveau du bit à-0.0
.Le cas de la multiplication
Dans le mode d'arrondi par défaut , aucun problème de ce type ne se produit avec
x*1.0
. Six
:x*1.0 == x
toujours.+/- infinity
, alors le résultat est+/- infinity
du même signe.est
NaN
, alors selonce qui signifie que l'exposant et la mantisse (mais pas le signe) de
NaN*1.0
sont recommandés inchangés par rapport à l'entréeNaN
. Le signe n'est pas spécifié conformément au §6.3p1 ci-dessus, mais une implémentation peut spécifier qu'il est identique à la sourceNaN
.+/- 0.0
, alors le résultat est a0
avec son bit de signe XORed avec le bit de signe de1.0
, en accord avec le §6.3p2. Puisque le bit de signe1.0
est0
, la valeur de sortie est inchangée par rapport à l'entrée. Ainsi,x*1.0 == x
même quandx
est un zéro (négatif).Le cas de la soustraction
Dans le mode d'arrondi par défaut , la soustraction
x-0.0
est également un no-op, car elle équivaut àx + (-0.0)
. Six
c'estNaN
, alors §6.3p1 et §6.2.3 s'appliquent à peu près de la même manière que pour l'addition et la multiplication.+/- infinity
, alors le résultat est+/- infinity
du même signe.x-0.0 == x
toujours.-0.0
donc au §6.3p2 que nous avons « [...] le signe d'une somme, ou d'une différence x - y considérée comme une somme x + (−y), diffère d'au plus un des signes d'addition; ". Cela nous oblige à attribuer-0.0
à la suite de(-0.0) + (-0.0)
, car le-0.0
signe ne diffère d' aucun des compléments, tandis que le+0.0
signe diffère de deux des compléments, en violation de cette clause.+0.0
, alors cela se réduit au cas d'addition(+0.0) + (-0.0)
considéré ci-dessus dans Le cas de l'addition , qui par §6.3p3 est censé donner+0.0
.Puisque dans tous les cas, la valeur d'entrée est légale comme sortie, il est permis de considérer
x-0.0
un no-op etx == x-0.0
une tautologie.Optimisations qui changent de valeur
La norme IEEE 754-2008 a la citation intéressante suivante:
Puisque tous les NaN et tous les infinis partagent le même exposant, et que le résultat correctement arrondi de
x+0.0
etx*1.0
pour finix
a exactement la même grandeur quex
, leur exposant est le même.sNaNs
Les NaN de signalisation sont des valeurs d'interruption à virgule flottante; Ce sont des valeurs NaN spéciales dont l'utilisation comme opérande à virgule flottante entraîne une exception d'opération non valide (SIGFPE). Si une boucle qui déclenche une exception était optimisée, le logiciel ne se comporterait plus de la même manière.
Cependant, comme le fait remarquer user2357112 dans les commentaires , le standard C11 laisse explicitement indéfini le comportement de signalisation NaNs (
sNaN
), de sorte que le compilateur est autorisé à supposer qu'ils ne se produisent pas, et donc que les exceptions qu'ils soulèvent ne se produisent pas non plus. Le standard C ++ 11 omet de décrire un comportement pour la signalisation des NaN, et le laisse donc également indéfini.Modes d'arrondi
Dans les modes d'arrondi alternés, les optimisations autorisées peuvent changer. Par exemple, en mode Round-to-Negative-Infinity , l'optimisation
x+0.0 -> x
devient admissible, maisx-0.0 -> x
devient interdite.Pour éviter que GCC n'assume les modes et comportements d'arrondi par défaut, le drapeau expérimental
-frounding-math
peut être passé à GCC.Conclusion
Clang et GCC , même à
-O3
, restent conformes à la norme IEEE-754. Cela signifie qu'il doit respecter les règles ci-dessus de la norme IEEE-754.x+0.0
n'est pas un peu identique àx
for allx
selon ces règles, maisx*1.0
peut être choisi comme tel : à savoir, lorsque nousx
quand il s'agit d'un NaN.* 1.0
.x
n'est pas un NaN.Pour activer l'optimisation IEEE-754-unsafe
(x+0.0) -> x
, l'indicateur-ffast-math
doit être passé à Clang ou GCC.la source
x += 0.0
n'est pas un NOOP six
c'est-0.0
. L'optimiseur pourrait de toute façon supprimer toute la boucle puisque les résultats ne sont pas utilisés, cependant. En général, il est difficile de dire pourquoi un optimiseur prend les décisions qu'il prend.la source
x += 0.0
n'est pas un non-op, mais je pensais que c'est probablement pas la raison parce que la boucle entière doit être optimisé sur toute façon. Je peux l'acheter, ce n'est tout simplement pas aussi convaincant que je l'espérais ...long long
l'optimisation est en vigueur (je l'ai fait avec gcc, qui se comporte de la même manière pour le double au moins)long long
est un type intégral, pas un type IEEE754.x -= 0
, est-ce la même chose?