Question

Les opérations en virgule flottante C # / .NET diffèrent-elles en précision entre les modes débogage et relâchement?

Était-ce utile?

La solution

Ils peuvent en effet être différents. Selon la spécification CLR ECMA:

  

Emplacements de stockage pour virgule flottante   nombres (statique, éléments de tableau et   champs de classes) sont de taille fixe.   Les tailles de stockage prises en charge sont   float32 et float64. Partout ailleurs   (sur la pile d'évaluation, comme   arguments, comme types de retour, et comme   variables locales) virgule flottante   les nombres sont représentés par un   type interne à virgule flottante. Dans chaque   Dans ce cas, le type nominal de   variable ou expression est soit R4 ou   R8, mais sa valeur peut être représentée   en interne avec une portée supplémentaire   et / ou précision. La taille de la   représentation interne en virgule flottante   dépend de la mise en œuvre, peut varier,   et doit avoir au moins la précision   grand comme celui de la variable ou   expression étant représentée. Un   élargissement implicite de la conversion à la   représentation interne de float32   ou float64 est effectuée lorsque ceux   les types sont chargés depuis le stockage. le   la représentation interne est généralement   la taille native du matériel, ou   au besoin pour efficace   mise en œuvre d'une opération.

En gros, cela signifie que la comparaison suivante peut être différente ou non:

class Foo
{
  double _v = ...;

  void Bar()
  {
    double v = _v;

    if( v == _v )
    {
      // Code may or may not execute here.
      // _v is 64-bit.
      // v could be either 64-bit (debug) or 80-bit (release) or something else (future?).
    }
  }
}

Message à retenir: ne jamais vérifier l’égalité des valeurs flottantes.

Autres conseils

C'est une question intéressante, j'ai donc fait un peu d'expérimentation. J'ai utilisé ce code:

static void Main (string [] args)
{
  float
    a = float.MaxValue / 3.0f,
    b = a * a;

  if (a * a < b)
  {
    Console.WriteLine ("Less");
  }
  else
  {
    Console.WriteLine ("GreaterEqual");
  }
}

utilisant DevStudio 2005 et .Net 2. J'ai compilé en tant que débogage et version et examiné la sortie du compilateur:

Release                                                    Debug

    static void Main (string [] args)                        static void Main (string [] args)
    {                                                        {
                                                        00000000  push        ebp  
                                                        00000001  mov         ebp,esp 
                                                        00000003  push        edi  
                                                        00000004  push        esi  
                                                        00000005  push        ebx  
                                                        00000006  sub         esp,3Ch 
                                                        00000009  xor         eax,eax 
                                                        0000000b  mov         dword ptr [ebp-10h],eax 
                                                        0000000e  xor         eax,eax 
                                                        00000010  mov         dword ptr [ebp-1Ch],eax 
                                                        00000013  mov         dword ptr [ebp-3Ch],ecx 
                                                        00000016  cmp         dword ptr ds:[00A2853Ch],0 
                                                        0000001d  je          00000024 
                                                        0000001f  call        793B716F 
                                                        00000024  fldz             
                                                        00000026  fstp        dword ptr [ebp-40h] 
                                                        00000029  fldz             
                                                        0000002b  fstp        dword ptr [ebp-44h] 
                                                        0000002e  xor         esi,esi 
                                                        00000030  nop              
      float                                                      float
        a = float.MaxValue / 3.0f,                                a = float.MaxValue / 3.0f,
00000000  sub         esp,0Ch                            00000031  mov         dword ptr [ebp-40h],7EAAAAAAh
00000003  mov         dword ptr [esp],ecx                
00000006  cmp         dword ptr ds:[00A2853Ch],0        
0000000d  je          00000014                            
0000000f  call        793B716F                            
00000014  fldz                                            
00000016  fstp        dword ptr [esp+4]                    
0000001a  fldz                                            
0000001c  fstp        dword ptr [esp+8]                    
00000020  mov         dword ptr [esp+4],7EAAAAAAh        
        b = a * a;                                                b = a * a;
00000028  fld         dword ptr [esp+4]                    00000038  fld         dword ptr [ebp-40h] 
0000002c  fmul        st,st(0)                            0000003b  fmul        st,st(0) 
0000002e  fstp        dword ptr [esp+8]                    0000003d  fstp        dword ptr [ebp-44h] 

      if (a * a < b)                                          if (a * a < b)
00000032  fld         dword ptr [esp+4]                    00000040  fld         dword ptr [ebp-40h] 
00000036  fmul        st,st(0)                            00000043  fmul        st,st(0) 
00000038  fld         dword ptr [esp+8]                    00000045  fld         dword ptr [ebp-44h] 
0000003c  fcomip      st,st(1)                            00000048  fcomip      st,st(1) 
0000003e  fstp        st(0)                                0000004a  fstp        st(0) 
00000040  jp          00000054                            0000004c  jp          00000052 
00000042  jbe         00000054                            0000004e  ja          00000056 
                                                        00000050  jmp         00000052 
                                                        00000052  xor         eax,eax 
                                                        00000054  jmp         0000005B 
                                                        00000056  mov         eax,1 
                                                        0000005b  test        eax,eax 
                                                        0000005d  sete        al   
                                                        00000060  movzx       eax,al 
                                                        00000063  mov         esi,eax 
                                                        00000065  test        esi,esi 
                                                        00000067  jne         0000007A 
      {                                                          {
        Console.WriteLine ("Less");                        00000069  nop              
00000044  mov         ecx,dword ptr ds:[0239307Ch]                Console.WriteLine ("Less");
0000004a  call        78678B7C                            0000006a  mov         ecx,dword ptr ds:[0239307Ch] 
0000004f  nop                                            00000070  call        78678B7C 
00000050  add         esp,0Ch                            00000075  nop              
00000053  ret                                                  }
      }                                                    00000076  nop              
      else                                                00000077  nop              
      {                                                    00000078  jmp         00000088 
        Console.WriteLine ("GreaterEqual");                      else
00000054  mov         ecx,dword ptr ds:[02393080h]              {
0000005a  call        78678B7C                            0000007a  nop              
      }                                                            Console.WriteLine ("GreaterEqual");
    }                                                    0000007b  mov         ecx,dword ptr ds:[02393080h] 
                                                        00000081  call        78678B7C 
                                                        00000086  nop              
                                                              }

Ce qui précède montre que le code en virgule flottante est identique pour le débogage et la publication. Le compilateur choisit la cohérence plutôt que l’optimisation. Bien que le programme produise un résultat erroné (a * a n'est pas inférieur à b), il est identique quel que soit le mode de débogage / libération.

Désormais, la FPU Intel IA32 dispose de huit registres à virgule flottante. On pourrait penser que le compilateur les utiliserait pour stocker les valeurs lors de l'optimisation plutôt que pour l'écriture en mémoire, améliorant ainsi les performances, comme suit:

fld         dword ptr [a] ; precomputed value stored in ram == float.MaxValue / 3.0f
fmul        st,st(0) ; b = a * a
; no store to ram, keep b in FPU
fld         dword ptr [a]
fmul        st,st(0)
fcomi       st,st(0) ; a*a compared to b

mais cela fonctionnerait différemment de la version de débogage (dans ce cas, afficher le résultat correct). Cependant, changer le comportement du programme en fonction des options de compilation est une très mauvaise chose.

Le code FPU est un domaine dans lequel le code peut être considérablement amélioré par rapport aux performances du compilateur, mais vous devez comprendre le fonctionnement du FPU.

En fait, ils peuvent différer si le mode débogage utilise le FPU x87 et si le mode de publication utilise SSE pour les opérations flottantes.

En réponse à la demande de Frank Krueger ci-dessus (dans les commentaires) visant à démontrer une différence:

Compilez ce code dans gcc sans optimisations et -mfpmath = 387 (je n'ai aucune raison de penser que cela ne fonctionnerait pas sur d'autres compilateurs, mais je ne l'ai pas essayé.) Ensuite, compilez-le sans optimisation et -msse -mfpmath = sse.

La sortie sera différente.

#include <stdio.h>

int main()
{
    float e = 0.000000001;
    float f[3] = {33810340466158.90625,276553805316035.1875,10413022032824338432.0};
    f[0] = pow(f[0],2-e); f[1] = pow(f[1],2+e); f[2] = pow(f[2],-2-e);
    printf("%s\n",f);
    return 0;
}

Voici un exemple simple où les résultats diffèrent non seulement entre le mode débogage et le mode de publication, mais la manière dont ils le font dépend de l’utilisation de la plate-forme x86 ou x84:

Single f1 = 0.00000000002f;
Single f2 = 1 / f1;
Double d = f2;
Console.WriteLine(d);

Ceci écrit les résultats suivants:

            Debug       Release
x86   49999998976   50000000199,7901
x64   49999998976   49999998976

Un rapide aperçu du désassemblage (Débogage - > Windows - > Désassemblage dans Visual Studio) donne quelques indications sur ce qui se passe ici. Pour le cas x86:

Debug                                       Release
mov         dword ptr [ebp-40h],2DAFEBFFh | mov         dword ptr [ebp-4],2DAFEBFFh  
fld         dword ptr [ebp-40h]           | fld         dword ptr [ebp-4]   
fld1                                      | fld1
fdivrp      st(1),st                      | fdivrp      st(1),st
fstp        dword ptr [ebp-44h]           |
fld         dword ptr [ebp-44h]           |
fstp        qword ptr [ebp-4Ch]           |
fld         qword ptr [ebp-4Ch]           |
sub         esp,8                         | sub         esp,8 
fstp        qword ptr [esp]               | fstp        qword ptr [esp]
call        6B9783BC                      | call        6B9783BC

En particulier, nous voyons qu'un groupe apparemment "redondant" stocke en mémoire la valeur du registre à virgule flottante, puis la recharge immédiatement dans le registre à virgule flottante " ont été optimisés en mode release. Cependant, les deux instructions

fstp        dword ptr [ebp-44h]  
fld         dword ptr [ebp-44h]

suffisent pour changer la valeur dans le registre x87 de + 5.0000000199790138e + 0010 à + 4.9999998976000000e + 0010 comme on peut le vérifier en effectuant le démontage et en recherchant les valeurs des registres correspondants (Debug - > Windows - > ; Registres, puis faites un clic droit et cochez la case "Virgule flottante").

L’histoire de x64 est extrêmement différente. Nous continuons de voir que la même optimisation supprime quelques instructions, mais cette fois-ci, tout repose sur SSE avec ses registres 128 bits et son jeu d’instructions dédiées:

Debug                                        Release
vmovss      xmm0,dword ptr [7FF7D0E104F8h] | vmovss      xmm0,dword ptr [7FF7D0E304C8h]  
vmovss      dword ptr [rbp+34h],xmm0       | vmovss      dword ptr [rbp-4],xmm0 
vmovss      xmm0,dword ptr [7FF7D0E104FCh] | vmovss      xmm0,dword ptr [7FF7D0E304CCh]
vdivss      xmm0,xmm0,dword ptr [rbp+34h]  | vdivss      xmm0,xmm0,dword ptr [rbp-4]
vmovss      dword ptr [rbp+30h],xmm0       |
vcvtss2sd   xmm0,xmm0,dword ptr [rbp+30h]  | vcvtss2sd   xmm0,xmm0,xmm0 
vmovsd      qword ptr [rbp+28h],xmm0       |
vmovsd      xmm0,qword ptr [rbp+28h]       |
call        00007FF81C9343F0               | call        00007FF81C9343F0 

Ici, comme l'unité SSE évite d'utiliser une précision plus élevée qu'une seule précision en interne (contrairement à l'unité x87), nous nous retrouvons avec le paramètre "simple précision". résultat de la casse x86 indépendamment des optimisations. En effet, on constate (après avoir activé les registres SSE dans la vue d'ensemble des registres de Visual Studio) qu'après X vdivss , XMM0 contient 0000000000000000-00000000513A43 qui est exactement le 49999998976 précédent.

Les deux divergences m'ont mordu dans la pratique. En plus d’illustrer qu’il ne faut jamais comparer l’égalité des points flottants, l’exemple montre également qu’il reste encore de la place pour le débogage d’assemblage dans un langage de haut niveau tel que C #, le moment où les points flottants apparaissent.

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