web-dev-qa-db-fra.com

Pourquoi Clang optimise-t-il 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);
}

(Balisage en C et C++ car je voudrais savoir si la réponse est différente pour chacun.)

125
Mehrdad

La norme IEEE 754-2008 pour l'arithmétique en virgule flottante et la Norme ISO/IEC 10967 Arithmétique indépendante du langage (LIA), partie 1 expliquez pourquoi.

IEEE 754 § 6.3 Le bit de signe

Lorsqu'une entrée ou un résultat est NaN, cette norme n'interprète pas le signe d'un NaN. Notez, cependant, que les opérations sur les chaînes de bits - copie, négation, abs, copySign - spécifient le bit de signe d'un résultat NaN, parfois basé sur le bit de signe d'un opérande NaN. Le prédicat logique totalOrder est également affecté par le bit de signe d'un opérande NaN. Pour toutes les autres opérations, cette norme ne spécifie pas le bit de signe d'un résultat NaN, même lorsqu'il n'y a qu'un seul NaN d'entrée, ou lorsque le NaN est produit à partir d'une opération non valide.

Lorsque ni les entrées ni le résultat ne sont NaN, le signe d'un produit ou d'un quotient est l'exclusif OR des signes des opérandes; le signe d'une somme, ou d'une différence x - y considéré comme une somme x + (−y), diffère d'au plus l'un des signes des addends; et le signe du résultat des conversions, l'opération de quantification, les opérations roundTo-Integral et roundToIntegralExact (voir 5.3.1) est le signe du premier ou du seul opérande. Ces règles s'appliquent même lorsque les opérandes ou les résultats sont nuls ou infinis.

Lorsque la somme de deux opérandes de signes opposés (ou la différence de deux opérandes de signes similaires) est exactement nulle, le signe de cette somme (ou différence) doit être +0 dans tous les attributs de direction d'arrondi sauf roundTowardNegative; sous cet attribut, le signe d'une somme (ou différence) nulle exacte doit être -0. Cependant, x + x = x −x) conserve le même signe que x même lorsque x est nul.

Le cas de l'addition

Sous le mode d'arrondi par défaut (Arrondi au plus proche, égal à égal), nous voyons que x+0.0 Produit x, SAUF lorsque x est -0.0: Dans ce cas, nous avons une somme de deux opérandes de signes opposés dont la somme est nulle, et §6.3 le paragraphe 3 règle que cet ajout produit +0.0.

Puisque +0.0 N'est pas au niveau du bit identique à l'original -0.0, Et que -0.0 Est une valeur légitime qui peut se produire 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é: sous le mode d'arrondi par défaut, dans x+0.0, Si x

Le cas de la multiplication

Sous le mode d'arrondi par défaut , aucun problème de ce type ne se produit avec x*1.0. Si x:

Le cas de la soustraction

Sous le mode d'arrondi par défaut , la soustraction x-0.0 Est également un no-op, car elle est équivalente à x + (-0.0). Si x est

Étant donné que dans tous les cas, la valeur d'entrée est légale en tant que sortie, il est permis de considérer x-0.0 Comme non-op et x == x-0.0 Une tautologie.

Optimisations de changement de valeur

La norme IEEE 754-2008 a la citation intéressante suivante:

IEEE 754 § 10.4 Signification littérale et optimisations de changement de valeur

[...]

Les transformations de modification de valeur suivantes, entre autres, préservent la signification littérale du code source:

Puisque tous les NaN et tous les infinis partagent le même exposant, et le résultat correctement arrondi de x+0.0 Et x*1.0 Pour fini x a exactement la même amplitude que x , leur exposant est le même.

sNaNs

Les NaN de signalisation sont des valeurs de piège à 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 user2357112 --- (souligne dans les commentaires, la norme C11 laisse explicitement indéfini le comportement de la signalisation des NaN (sNaN), donc le compilateur est autorisés à supposer qu'elles ne se produisent pas, et donc que les exceptions qu'elles soulèvent ne se produisent pas non plus. Le standard C++ 11 omet de décrire un comportement de signalisation des NaN, et le laisse donc également indéfini.

Modes d'arrondi

Dans les autres modes d'arrondi, les optimisations autorisées peuvent changer. Par exemple, en mode Round-to-Negative-Infinity , l'optimisation x+0.0 -> x Devient admissible, mais x-0.0 -> x Devient interdit.

Pour empêcher GCC d'assumer les modes et comportements d'arrondi par défaut, l'indicateur expérimental -frounding-math Peut être transmis à GCC.

Conclusion

Clang et GCC , même à -O3, Reste conforme à 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 pas identique à x pour tous x selon ces règles, mais x*1.0 peut être choisi pour être ainsi: À savoir, quand nous

  1. Obéissez à la recommandation de laisser inchangée la charge utile de x lorsqu'il s'agit d'un NaN.
  2. Laissez le bit de signe d'un résultat NaN inchangé par * 1.0.
  3. Obéissez à l'ordre XOR le bit de signe pendant un quotient/produit, lorsque x est pas un NaN.

Pour activer l'optimisation IEEE-754 non sûre (x+0.0) -> x, L'indicateur -ffast-math Doit être transmis à Clang ou GCC.

163

x += 0.0 n'est pas un NOOP si x est -0.0 . L'optimiseur pourrait de toute façon supprimer la boucle entière 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 qu'il prend.

35
user2357112