125 votes

Pourquoi Clang optimise-t-il loin x * 1.0 mais PAS x + 0.0?

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);
}
 

(Marquer comme C et C ++ parce que je voudrais savoir si la réponse est différente pour chacun.)

163voto

La Norme IEEE 754-2008 de l'Arithmétique à virgule Flottante et la norme ISO/IEC 10967 Indépendante du Langage de l'Arithmétique (LIA), Partie 1 réponse pourquoi il en est ainsi.

La norme IEEE 754 § 6.3 Le bit de signe

Quand une entrée ou le résultat est NaN, cette norme n'a pas d'interpréter le signe d'un NaN. Notez, cependant, que les opérations sur les chaînes de bit - de copier, de le nier, abs, copySign - spécifier le bit de signe d'un NaN résultat, parfois basé sur le bit de signe d'un NaN opérande. La logique de prédicat totalOrder est également affectée par le bit de signe d'un NaN opérande. Pour toutes les autres opérations, la présente norme ne spécifie pas le bit de signe d'un NaN résultat, même quand il n'y a qu'une seule entrée NaN, ou lorsque le NaN est produit à partir d'une opération non valide.

Lorsque ni entrées ni le résultat est NaN, le signe d'un produit ou d'un quotient est le OU exclusif des opérandes " des signes, le signe d'une somme ou d'une différence x − y considérée comme une somme x + (−y), diffère de la plupart l'un des addends " signes; et le signe du résultat des conversions, la quantification de l'opération, le roundTo Intégrale des opérations, et la roundToIntegralExact (voir 5.3.1) est le signe le premier ou le seul opérande. Ces règles s'appliquent même lorsque les opérandes ou les résultats sont à zéro ou l'infini.

Lorsque la somme des deux opérandes avec des signes opposés (ou la différence de deux opérandes avec comme signes) est exactement zéro, le signe de la somme (ou la différence) doit être +0 dans tous les arrondissements de la direction attributs, sauf roundTowardNegative; en vertu de cet attribut, le signe d'un zéro exacte de la somme (ou la différence) est de 0. Cependant, x + x = x − (−x) conserve le même signe que x, même lorsque x est égal à zéro.

Le Cas de l'Addition

Nous voyons qu' x+0.0 produit x, SAUF lorsqu' x est -0.0: Dans ce cas, nous avons une somme de deux opérandes avec des signes opposés dont la somme est égale à zéro, et le §6.3 paragraphe 3 des règles de cet ajout de produit +0.0 dans le mode d'arrondi par défaut (arrondi au plus Proche, Liens-de-Même).

Depuis +0.0 n'est pas au niveau du bit identique à l'original, -0.0, et qu' -0.0 est une valeur légitime qui peut se produire en entrée, le compilateur est obligé de mettre dans le code qui va transformer le négatif potentiel des zéros d' +0.0.

Le résumé: dans le mode d'arrondi par défaut, en x+0.0, si x

  • n'est pas -0.0, alors x lui-même est acceptable de la valeur de sortie.
  • est - -0.0, alors la valeur de sortie doit être +0.0, ce qui n'est pas au niveau du bit identique à -0.0.

Le Cas de la Multiplication

Pas ce problème se produit avec x*1.0. Si x:

  • est une (sous -) nombre normal, x*1.0 == x toujours.
  • est - +/- infinity, alors le résultat est +/- infinity de même signe.
  • est - NaN, alors, selon

    La norme IEEE 754 § 6.2.3 NaN Propagation

    Une opération qui se propage un NaN opérande à sa suite et a un seul NaN comme une entrée doit produire un NaN avec la charge utile de l'entrée NaN si représentable dans le format de destination.

    ce qui signifie que l'exposant et la mantisse (mais pas le signe) NaN*1.0 sont recommandés inchangés par rapport à l'entrée NaN. Le signe n'est pas spécifié, conformément au §6.3p1 ci-dessus, mais une mise en œuvre peut préciser qu'il soit identique à la source NaN.

  • est - +/- 0, alors le résultat est un 0 avec son bit de signe XORed avec le bit de signe d' 1.0, en accord avec le §6.3p2. Depuis, le bit de signe d' 1.0 est 0, la valeur de sortie est inchangé par rapport à l'entrée. Ainsi, x*1.0 == x même lorsqu' x est un (négatif) à zéro.

La Valeur De Changer Les Optimisations

La Norme IEEE 754-2008 a la suite de citation intéressante:

§ La norme IEEE 754 10.4 signification Littérale et la valeur de changer les optimisations

[...]

La valeur suivante-évolution des transformations, entre autres, de préserver le sens littéral du code source:

  • L'application de la propriété d'identité 0 + x lorsque x n'est pas nul et n'est pas une signalisation NaN et le résultat est le même exposant comme x.
  • L'application de la propriété d'identité 1 × x lorsque x n'est pas une signalisation NaN et le résultat est le même exposant comme x.
  • La modification de la charge ou de bit de signe d'un calme NaN.
  • [...]

Depuis tous les NaNs et tous les infinis partager le même exposant, et l'correctement résultat arrondi de la x+0.0 et x*1.0 pour finie x a exactement le même ordre de grandeur que x, leur exposant est le même.

sNaNs

Signalisation NaNs sont à virgule flottante valeurs de recouvrement; qu'Ils sont spéciaux NaN valeurs dont l'utilisation en tant que virgule flottante opérande résultats dans une opération non valide exception (SIGFPE). Si une boucle qui déclenche une exception a été optimisé, le logiciel ne se comportent plus de la même.

Cependant, comme user2357112 points dans les commentaires, la Norme C11 explicitement laisse pas défini le comportement de la signalisation des NaNs (sNaN), de sorte que le compilateur est permis de supposer qu'ils ne se produisent pas, et donc que les exceptions qu'ils soulèvent aussi de ne pas se produire. Le C++11 standard omet de décrire un comportement pour la signalisation NaNs, et donc aussi les feuilles indéfini.

Conclusion

Clang et GCC, même à l' -O3, reste la norme IEEE-754 conforme. Cela signifie qu'il doit s'en tenir aux règles ci-dessus de la norme IEEE-754. x+0.0 est pas de bits identiques à x tous x en vertu de ces règles, mais x*1.0 peut-être choisi de l'être: à Savoir, lorsque nous obéissons à 1), La recommandation de passer inchangé à la charge de l' x quand c'est un NaN, et 2) nous obéir à l'ordre de XOR, le bit de signe au cours d'un quotient ou un produit, lorsqu' x est pas un NaN.

Pour permettre à tous de dangereux optimisations, -ffast-math doit être passé.

35voto

user2357112 Points 37737

x += 0.0 n'est pas une NOOP si x est -0.0 . L'optimiseur peut toutefois supprimer toute la boucle car les résultats ne sont pas utilisés. En général, il est difficile de dire pourquoi un optimiseur prend les décisions.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X