47 votes

Forcer la virgule flottante à être déterministe dans .NET?

J'ai lu beaucoup de choses sur virgule flottante déterminisme dans .NET, c'est à dire veiller à ce que le même code avec les mêmes entrées donnera les mêmes résultats sur différentes machines. Depuis .NET manque d'options comme Java fpstrict et MSVC de la fp:strict, le consensus semble être que il n'y a aucun moyen de contourner ce problème en utilisant uniquement du code managé. Le C# de jeu de l'IA de la guerre est réglé sur l'utilisation de la virgule Fixe les maths à la place, mais c'est une lourde solution.

Le principal problème semble être que le CLR permet de résultats intermédiaires à vivre dans les registres FPU qui ont une précision plus élevée que le type natif de précision, conduisant à impredictably plus grande précision des résultats. Un article MSDN par CLR ingénieur David Notario explique ce qui suit:

Notez qu'avec la spécialisation actuelle, c'est toujours un choix de la langue pour donner ‘la prévisibilité'. La langue peut insérer conv.r4 ou conv.r8 instructions après chaque FP pour obtenir un ‘prévisible' comportement. Évidemment, c'est très cher, et dans des langues différentes divers compromis. C#, par exemple, ne fait rien, si vous voulez rétrécissement, vous aurez à insérer (float) et (double) jette à la main.

Cela donne à penser que l'on peut atteindre à virgule flottante déterminisme simplement en insérant des conversions explicites pour chaque expression et la sous-expression qui s'évalue à flotteur. On pourrait écrire un wrapper type autour de flotteur pour automatiser cette tâche. Ce serait une solution simple et idéale!

D'autres observations suggèrent cependant qu'il n'est pas si simple. Eric Lippert a récemment déclaré (l'emphase est mienne):

dans certaines versions du moteur d'exécution, de casting pour flotter donne explicitement une autre résultat que de ne pas le faire. Lorsque vous effectuez un cast explicite de flotter, le compilateur C# donne une astuce pour l'exécution de dire "prendre cette chose de très haute précision en mode si vous arrive d'être en utilisant cette l'optimisation".

Quelle est cette "astuce" de l'exécution? Le C# spec prévoir qu'un cast explicite float provoque l'insertion d'une conv.r4 dans l'-t-IL? Le CLR spec stipulent qu'un conv.r4 instruction provoque une valeur à être réduit à sa taille d'origine? Seulement si ces deux sont vrai peut-on compter sur les conversions explicites de fournir à virgule flottante "prévisibilité", comme l'explique David Notario.

Enfin, même si on peut en effet contraindre tous les résultats intermédiaires du type de la taille d'origine, est-ce suffisant pour garantir la reproductibilité dans les machines, ou il y en a d'autres facteurs, comme la FPU/SSE paramètres d'exécution?

28voto

Eric Lippert Points 300275

Quelle est cette "astuce" de l'exécution?

Comme vous le conjecture, le compilateur vérifie si une conversion en double ou float a été effectivement présent dans le code source, et si elle l'est, elle toujours insère le approprié conv opcode.

Le C# spec prévoir qu'un cast explicite float provoque l'insertion d'une conv.r4 dans l'-t-IL?

Non, mais je vous assure qu'il y a des tests unitaires dans le compilateur de cas de test que de s'assurer qu'il n'. Bien que la spécification ne demande pas cela, vous pouvez compter sur ce comportement.

La spécification du seul commentaire est que toute opération de virgule flottante peut être fait dans un plus de précision que nécessaire en fonction de l'humeur du moment de l'exécution, et que cela peut rendre vos résultats de façon inattendue plus précis. Voir la section 4.1.6.

Le CLR spec stipulent qu'un conv.r4 instruction provoque une valeur à être réduit à sa taille d'origine?

Oui, dans la Partition I, section 12.1.3, ce qui je note , vous pourriez avoir cherché vous-même plutôt que de demander à l'internet pour le faire pour vous. Ces spécifications sont gratuits sur le web.

Une question que vous n'avez pas demandé, mais devrait probablement avoir:

Est-il de toute opération autre que le casting qui tronque les flotteurs de haute précision de mode?

Oui. L'affectation à un champ statique, champ d'instance ou d'un élément d'un double[] ou float[] tableau tronque.

Est conforme troncature suffisant pour garantir la reproductibilité dans les machines?

Pas de. Je vous encourage à lire l'article 12.1.3, qui a beaucoup d'intéressant à dire sur le sujet de denormals et NaNs.

Et enfin, une autre question que vous ne demandez pas, mais devrait probablement avoir:

Comment puis-je garantir reproductible de l'arithmétique?

Utiliser les nombres entiers.

25voto

Hans Passant Points 475940

Le 8087 Floating Point Unit la conception de la puce a été d'Intel milliards de dollars erreur. L'idée semble bonne sur le papier, donnez-lui un 8 pile de registre qui stocke les valeurs en précision étendue, 80 bits. De sorte que vous pouvez écrire les calculs dont les valeurs intermédiaires sont moins susceptibles de perdre de chiffres significatifs.

La bête est cependant impossible pour l'optimiser. Stocker une valeur dans la pile FPU retour à la mémoire est chère. Donc les garder à l'intérieur de la FPU est une forte but d'optimisation. Inévitable, n'ayant que 8 registres va nécessiter une reprise si le calcul est assez profonde. Il est également mis en œuvre comme une pile, ne sont pas librement adressable registres, de sorte que nécessite la gymnastique ainsi que peut produire un write-back. Inévitablement, une écriture en arrière tronquer la valeur à partir de 80 bits à 64 bits, perdre de la précision.

Donc, les conséquences sont que les non-optimisé code ne produit pas le même résultat que le code optimisé. Et petits changements au calcul peut avoir de grands effets sur le résultat quand une valeur intermédiaire doivent être réécrites. L' /fp:option strict est un hack autour de cela, il force le générateur de code pour émettre un write-back de conserver les valeurs cohérentes, mais avec l'inévitable et que des pertes de perf.

C'est une roche et un endroit dur. Pour le x86 gigue ils n'essayez pas de résoudre le problème.

Intel n'a pas fait la même erreur quand ils ont conçu le jeu d'instructions SSE. Les registres XMM sont librement adressable et de ne pas stocker des bits supplémentaires. Si vous voulez des résultats cohérents puis compiler avec le AnyCPU cible, et un système d'exploitation 64 bits, est la solution rapide. Le x64 gigue utilise de l'ESS au lieu de FPU instructions de calcul en virgule flottante. Bien que cet ajout d'une troisième voie qu'un calcul peut produire un résultat différent. Si le calcul est faux, car il perd aussi le nombre de chiffres significatifs alors il sera toujours mauvais. Ce qui est un peu un bromure, vraiment, mais en général seulement dans la mesure où un programmeur regarde.

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