Bienvenue dans le monde de virgule flottante dénormalisée ! Ils peuvent faire des ravages sur les performances ! !!
Les nombres dénormaux (ou subnormaux) sont une sorte d'astuce pour obtenir des valeurs supplémentaires très proches de zéro à partir de la représentation en virgule flottante. Les opérations sur les nombres à virgule flottante dénormalisés peuvent être des dizaines ou des centaines de fois plus lent que sur la virgule flottante normalisée. En effet, de nombreux processeurs ne peuvent pas les gérer directement et doivent les piéger et les résoudre à l'aide d'un microcode.
Si vous imprimez les nombres après 10 000 itérations, vous verrez qu'ils ont convergé vers des valeurs différentes selon que 0
o 0.1
est utilisé.
Voici le code de test compilé sur x64 :
int main() {
double start = omp_get_wtime();
const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6};
const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690};
float y[16];
for(int i=0;i<16;i++)
{
y[i]=x[i];
}
for(int j=0;j<9000000;j++)
{
for(int i=0;i<16;i++)
{
y[i]*=x[i];
y[i]/=z[i];
#ifdef FLOATING
y[i]=y[i]+0.1f;
y[i]=y[i]-0.1f;
#else
y[i]=y[i]+0;
y[i]=y[i]-0;
#endif
if (j > 10000)
cout << y[i] << " ";
}
if (j > 10000)
cout << endl;
}
double end = omp_get_wtime();
cout << end - start << endl;
system("pause");
return 0;
}
Sortie :
#define FLOATING
1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007
1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007
//#define FLOATING
6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.46842e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044
6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.45208e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044
Notez comment dans la deuxième série, les chiffres sont très proches de zéro.
Les nombres dénormalisés sont généralement rares et la plupart des processeurs n'essaient donc pas de les traiter efficacement.
Pour démontrer que cela a tout à voir avec les nombres dénormalisés, si nous Remettre les dénormaux à zéro en ajoutant ceci au début du code :
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Ensuite, la version avec 0
n'est plus 10x plus lent et devient même plus rapide. (Cela nécessite que le code soit compilé avec SSE activé).
Cela signifie que, plutôt que d'utiliser ces étranges valeurs de précision inférieure presque nulles, nous arrondissons simplement à zéro.
Horaires : Core i7 920 @ 3.5 GHz :
// Don't flush denormals to zero.
0.1f: 0.564067
0 : 26.7669
// Flush denormals to zero.
0.1f: 0.587117
0 : 0.341406
En fin de compte, cela n'a rien à voir avec le fait qu'il s'agisse d'un nombre entier ou à virgule flottante. Le site 0
o 0.1f
est converti/stocké dans un registre en dehors des deux boucles. Cela n'a donc aucun effet sur les performances.
2 votes
Assurez-vous que vous construisez un build de version, pas de débogage.
11 votes
Comment avez-vous mesuré la différence ? Et quelles options avez-vous utilisées lors de la compilation ?
166 votes
Pourquoi le compilateur ne laisse-t-il pas tomber le +/- 0 dans ce cas ? !?
5 votes
Cette question est étroitement liée à celle de l'affaire stackoverflow.com/questions/5180150/
129 votes
@Zyx2000 Le compilateur n'est pas du tout aussi stupide. Le désassemblage d'un exemple trivial dans LINQPad montre qu'il produit le même code, que l'on utilise
0
,0f
,0d
ou encore(int)0
dans un contexte où undouble
est nécessaire.1 votes
Voir la différence ici diffchecker.com/Rmf9561
17 votes
Quel est le niveau d'optimisation ?
3 votes
Otto Allmendinger le niveau d'optimisation que j'ai utilisé était 02 avec sse2 activé.
2 votes
@Dragarro - Juste par curiosité - comment avez-vous fini par écrire et chronométrer ce code ?
1 votes
@Vic J'étais juste en train de faire l'idiot quand je l'ai remarqué, en chronométrant différentes fonctions et en vérifiant différents drapeaux d'optimisation du compilateur. Au début, le code n'avait que la partie multiplication et division, et j'ai pensé à ajouter l'addition et la soustraction juste pour voir combien de temps supplémentaire cela prendrait, et à ma surprise, le code avait accéléré d'un facteur 10. J'ai vérifié pour voir si je n'avais pas écrit quelque chose de bizarre, et j'ai fini par le poster ici pour voir si je n'avais pas manqué quelque chose.
1 votes
@HamidNazari - Une base de données de différences en ligne est une bonne idée, mais celle-ci semble renvoyer "can't open file". Il semble que la valeur par défaut soit "Don't store diff", j'ai sélectionné "store forever" et cela m'a donné ce lien : diffchecker.com/U6w74qj
1 votes
Ver stackoverflow.com/questions/5180150/
17 votes
Pourquoi, vraiment, le compilateur n'abandonne-t-il pas les +/-0 ?
0 votes
En réalité, j'espère qu'aujourd'hui, presque 10 ans plus tard, les compilateurs génèrent simplement du code qui produit les valeurs...