Ce qui pourrait provoquer un processus déterministe pour générer des erreurs à virgule flottante

StackOverflow https://stackoverflow.com/questions/968435

Question

Ayant déjà lu cette Je suis raisonnablement certain qu'une donnée procédé utilisant arithmatic à virgule flottante avec la même entrée (sur le même matériel, compilé avec le même compilateur) doit être déterministe. Je regarde un cas où ce n'est pas vrai et d'essayer de déterminer ce qui aurait pu causer cela.

J'ai compilé un fichier exécutable et je nourrir les mêmes données exactes, en cours d'exécution sur une seule machine (non multithread) mais je reçois des erreurs d'environ 3.814697265625e-06 qui, après googler attention je trouve est en fait égal à 1/4 de ^ 9 = 1/2 ^ 18 = 1/262144. qui est à peu près le niveau de précision d'un nombre à virgule flottante de 32 bits (à 7 chiffres selon wikipedia)

Je soupçonne qu'il a quelque chose à voir avec Optimisations qui ont été appliquées au code. J'utilise le compilateur C ++ de intel et ont tourné la spéculation à virgule flottante rapide au lieu de sécurité ou stricte. Cela pourrait-il faire un processus de virgule flottante non déterministe? Y at-il d'autres Optimisations etc qui pourraient conduire à ce comportement?

EDIT : Selon la suggestion de Pax Je recompilé le code avec la spéculation à virgule flottante est tourné vers la sécurité et je suis maintenant obtenir des résultats stables. Cela me permet de clarifier cette question - qu'est-ce que spéculation à virgule flottante ne fait et comment cela peut provoquer le même binaire (à savoir une compilation, des pistes multiples) pour générer des résultats différents lorsqu'ils sont appliqués à l'entrée exacte même

@Ben Je compile en utilisant Intel (R) C ++ 11.0.061 [IA-32] et Im fonctionnant sur un processeur Intel quadcore.

Était-ce utile?

La solution

Dans presque toutes les situations où il y a un mode rapide et un mode sans échec, vous trouverez un compromis de quelque sorte. Sinon, tout irait en mode rapide en toute sécurité: -.)

Et, si vous obtenez des résultats différents avec la même entrée, votre processus est pas déterministe, peu importe à quel point vous croyez qu'il est (malgré les preuves empiriques).

Je dirais que votre explication est la plus probable. Mettez-le en mode sans échec et voir si le non-déterminisme disparaît. Cela vous dire à coup sûr.

Quant à savoir s'il y a d'autres optimisations, si vous compilez sur le même matériel avec le même compilateur / éditeur de liens et les mêmes options à ces outils , il devrait générer un code identique. Je ne vois aucune autre possibilité autre que le mode rapide (ou pourriture bits dans la mémoire due aux rayons cosmiques, mais c'est assez peu probable).

Après votre mise à jour:

Intel a un document qui explique certaines des choses qu'ils sont pas autorisés à le faire en mode sans échec, y compris mais sans s'y limiter:

  • réassociation. (a+b)+c -> a+(b+c)
  • zéro pliage. x + 0 -> x, x * 0 -> 0
  • multiplication réciproque. a/b -> a*(1/b)

Alors que vous déclarez que ces opérations sont la compilation définies, les puces Intel sont assez sacrément intelligent. Ils peuvent modifier l'ordre des instructions pour garder les pipelines plein dans plusieurs CPU set-up donc, à moins que le code interdit spécifiquement un tel comportement, les choses peuvent changer à l'exécution (pas de compilation) pour garder les choses à pleine vitesse.

Ceci est couvert (brièvement) à la page 15 de ce document lié qui parle de vectorisation ( « Problème: résultats différents réexécution du même binaire sur les mêmes données sur le même processeur » ).

Mon conseil serait de décider si vous avez besoin grognement brut ou total de résultats reproductibilité puis choisissez le mode sur cette base.

Autres conseils

Si votre programme est parallélisé, car il est peut-être à courir sur un quad core, il peut bien être non-déterministe.

Imaginez que vous avez 4 processeurs en ajoutant une valeur en virgule flottante au même emplacement mémoire. Ensuite, vous pourriez obtenir

(((InitialValue+P1fp)+P2fp)+P3fp)+P4fp

ou

(((InitialValue+P2fp)+P3fp)+P1fp)+P4fp

ou l'un des autres ordres possibles.

Heck, vous pouvez même obtenir

 InitialValue+(P2fp+P3fp)+(P1fp+P4fp)

si le compilateur est assez bon.

Malheureusement, l'addition à virgule flottante n'est pas commutative ou associative. arithmétique nombre réel est, mais virgule flottante n'est pas, en raison de l'arrondissement, trop-plein et underflow.

En raison de cela, le calcul parallèle FP est souvent non déterministe. « Souvent », parce que les programmes qui ressemblent à

  on each processor
    while( there is work to do ) {
       get work
       calculate result
       add to total 
    }

est non-déterministe, car la quantité de temps que chacun prend peut varier considérablement - vous ne pouvez pas prédire l'ordre des opérations. (Pire si les fils interagissent.)

Mais pas toujours, car il y a des styles de programmation parallèle déterministes.

Bien sûr, ce que beaucoup de gens qui se soucient de déterminisme faire est de travailler en entier ou point fixe pour éviter le problème. Je suis particulièrement friand de superaccumulators, 512, 1024 ou 2048 numéros de bits que les nombres à virgule flottante peuvent être ajoutés à, sans erreurs d'arrondi souffrance.


En ce qui concerne une seule application filetée: le compilateur peut réorganiser le code. Différentes compilations peuvent donner des réponses différentes. Mais tout binaire particulière doit être déterministe.

A moins que ... vous travaillez dans un langage dynamique. Qui effectue optimizatuions que réordonner les calculs FP, qui varient au fil du temps.

Ou à moins que ... tir très longue: Itanium avait quelques fonctionnalités, comme l'ALAT, qui ont fait même un seul thread code non déterministe. Il est peu probable d'être affectés par ces derniers.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top