1510 votes

Pourquoi changer de 0,1 f 0 ralentir les performances en 10x?

Pourquoi ce bout de code,

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];
        y[i] = y[i] + 0.1f; // <--
        y[i] = y[i] - 0.1f; // <--
    }
}

exécuter plus de 10 fois plus rapide que le bit suivant (identique sauf en cas d'indication)?

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];
        y[i] = y[i] + 0; // <--
        y[i] = y[i] - 0; // <--
    }
}

lors de la compilation avec Visual Studio 2010 SP1. (Je n'ai pas testé avec d'autres compilateurs.)

1599voto

Mysticial Points 180300

Bienvenue dans le monde de dénormalisée à virgule flottante! Ils peuvent faire des ravages sur les performances!!!

Nombres dénormalisés (ou subnormale) les chiffres sont une sorte de hack pour obtenir une partie supplémentaire des valeurs très proches de zéro de la représentation à virgule flottante. Opérations sur dénormalisée à virgule flottante peut être des dizaines à des centaines de fois plus lent que sur normalisé à virgule flottante. C'est parce que de nombreux processeurs ne pouvons pas les traiter directement et de les capturer et de les résoudre à l'aide de microcode.

Si vous imprimez le ou les numéros après 10 000 itérations, vous verrez qu'ils ont convergé pour des valeurs différentes selon qu' 0 ou 0.1 est utilisé.

Voici le test de code 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

Remarquez dans la deuxième manche, les chiffres sont très proches de zéro.

Les nombres dénormalisés sont généralement rares et donc la plupart des processeurs n'essayez pas de les gérer efficacement.


Pour démontrer que cela a tout à voir avec les nombres dénormalisés, si nous rincer denormals à zéro en ajoutant ceci au début du code:

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

Puis la version avec 0 n'est plus de 10x plus lent et devient de plus en plus vite. (Cela nécessite que le code soit compilé avec l'ESS est activé.)

Cela signifie que, plutôt que d'utiliser ces étranges précision inférieure presque valeurs différentes de zéro, nous avons juste round à zéro à la place.

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 vraiment rien à voir avec le fait que c'est un nombre entier ou à virgule flottante. L' 0 ou 0.1f est converti ou stockés dans un registre à l'extérieur de chacune des deux boucles. Donc cela n'a aucun effet sur les performances.

414voto

mvds Points 26475

À l'aide de gcc et en appliquant un diff à l'assembly généré des rendements de seulement cette différence:

73c68,69
<   movss   LCPI1_0(%rip), %xmm1
---
>   movabsq $0, %rcx
>   cvtsi2ssq   %rcx, %xmm1
81d76
<   subss   %xmm1, %xmm0

L' cvtsi2ssq étant 10 fois plus lent en effet.

Apparemment, l' float version utilise un XMM registre chargé de la mémoire, tandis que l' int version convertit un réel int de la valeur 0 à float à l'aide de l' cvtsi2ssq d'instructions, de prendre beaucoup de temps. En passant -O3 de gcc ne l'aide pas. (version de gcc 4.2.1.)

(À l'aide d' double au lieu de float n'a pas d'importance, sauf qu'il remplace l' cvtsi2ssq en cvtsi2sdq.)

Mise à jour

Certains tests montrent que ce n'est pas nécessairement cvtsi2ssq enseignement. Une fois éliminés (à l'aide d'un int ai=0;float a=ai; et à l'aide de a au lieu de 0), la différence de vitesse reste. Donc @Mysticial est droit, le dénormalisée flotteurs de faire la différence. Ceci peut être vu par les tests de valeurs entre 0 et 0.1f. Le point tournant dans le code ci-dessus est à environ 0.00000000000000000000000000000001, lorsque les boucles soudain prend 10 fois plus longtemps.

Mise à jour<<1

Une petite visualisation de ce phénomène intéressant:

  • Colonne 1: un flotteur, divisé par 2 à chaque itération
  • Colonne 2: la représentation binaire de ce flotteur
  • Colonne 3: le temps pris pour faire la somme de ce flotteur 1e7 fois

Vous pouvez clairement voir l'exposant (le dernier 9 bits) variation de sa valeur la plus basse, lors de la dénormalisation. À ce moment, la simple addition devient 20 fois plus lent.

0.000000000000000000000000000000000100000004670110: 10111100001101110010000011100000 45 ms
0.000000000000000000000000000000000050000002335055: 10111100001101110010000101100000 43 ms
0.000000000000000000000000000000000025000001167528: 10111100001101110010000001100000 43 ms
0.000000000000000000000000000000000012500000583764: 10111100001101110010000110100000 42 ms
0.000000000000000000000000000000000006250000291882: 10111100001101110010000010100000 48 ms
0.000000000000000000000000000000000003125000145941: 10111100001101110010000100100000 43 ms
0.000000000000000000000000000000000001562500072970: 10111100001101110010000000100000 42 ms
0.000000000000000000000000000000000000781250036485: 10111100001101110010000111000000 42 ms
0.000000000000000000000000000000000000390625018243: 10111100001101110010000011000000 42 ms
0.000000000000000000000000000000000000195312509121: 10111100001101110010000101000000 43 ms
0.000000000000000000000000000000000000097656254561: 10111100001101110010000001000000 42 ms
0.000000000000000000000000000000000000048828127280: 10111100001101110010000110000000 44 ms
0.000000000000000000000000000000000000024414063640: 10111100001101110010000010000000 42 ms
0.000000000000000000000000000000000000012207031820: 10111100001101110010000100000000 42 ms
0.000000000000000000000000000000000000006103515209: 01111000011011100100001000000000 789 ms
0.000000000000000000000000000000000000003051757605: 11110000110111001000010000000000 788 ms
0.000000000000000000000000000000000000001525879503: 00010001101110010000100000000000 788 ms
0.000000000000000000000000000000000000000762939751: 00100011011100100001000000000000 795 ms
0.000000000000000000000000000000000000000381469876: 01000110111001000010000000000000 896 ms
0.000000000000000000000000000000000000000190734938: 10001101110010000100000000000000 813 ms
0.000000000000000000000000000000000000000095366768: 00011011100100001000000000000000 798 ms
0.000000000000000000000000000000000000000047683384: 00110111001000010000000000000000 791 ms
0.000000000000000000000000000000000000000023841692: 01101110010000100000000000000000 802 ms
0.000000000000000000000000000000000000000011920846: 11011100100001000000000000000000 809 ms
0.000000000000000000000000000000000000000005961124: 01111001000010000000000000000000 795 ms
0.000000000000000000000000000000000000000002980562: 11110010000100000000000000000000 835 ms
0.000000000000000000000000000000000000000001490982: 00010100001000000000000000000000 864 ms
0.000000000000000000000000000000000000000000745491: 00101000010000000000000000000000 915 ms
0.000000000000000000000000000000000000000000372745: 01010000100000000000000000000000 918 ms
0.000000000000000000000000000000000000000000186373: 10100001000000000000000000000000 881 ms
0.000000000000000000000000000000000000000000092486: 01000010000000000000000000000000 857 ms
0.000000000000000000000000000000000000000000046243: 10000100000000000000000000000000 861 ms
0.000000000000000000000000000000000000000000022421: 00001000000000000000000000000000 855 ms
0.000000000000000000000000000000000000000000011210: 00010000000000000000000000000000 887 ms
0.000000000000000000000000000000000000000000005605: 00100000000000000000000000000000 799 ms
0.000000000000000000000000000000000000000000002803: 01000000000000000000000000000000 828 ms
0.000000000000000000000000000000000000000000001401: 10000000000000000000000000000000 815 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 44 ms

Un équivalent de discussion sur le BRAS peut être trouvé dans le Débordement de la Pile question Dénormalisée à virgule flottante en Objective-C?.

34voto

fig Points 147

C'est en raison de dénormalisée floating-point. Comment se débarrasser de tous les deux et la perte de performance? Après avoir parcouru l'Internet pour les moyens de tuer tout nombre dénormalisé, il semble qu'il n'y a pas de "meilleure" façon de le faire encore. J'ai trouvé ces trois méthodes peuvent mieux travailler dans différents environnements:

  • Peut ne pas fonctionner dans certains GCC environnements:

    // Requires #include <fenv.h>
    fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
    
  • Peut ne pas fonctionner dans certains Visual Studio: 1

    // Requires #include <xmmintrin.h>
    _mm_setcsr( _mm_getcsr() | (1<<15) | (1<<6) );
    // Does both FTZ and DAZ bits. You can also use just hex value 0x8040 to do both.
    // You might also want to use the underflow mask (1<<11)
    
  • Semble fonctionner dans les deux GCC et Visual Studio:

    // Requires #include <xmmintrin.h>
    // Requires #include <pmmintrin.h>
    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
    _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
    
  • Le compilateur Intel a des options pour désactiver denormals par défaut sur le moderne, les Processeurs Intel. Plus de détails ici

  • Commutateurs de compilateur. -ffast-math, -msse ou -mfpmath=sse de désactiver denormals et faire quelques autres choses plus vite, mais aussi, malheureusement, à faire beaucoup d'autres approximations qui pourraient casser votre code. Test avec soin! L'équivalent de fast-mathématiques pour le compilateur Visual Studio est - /fp:fast mais je n'ai pas été en mesure de confirmer si cela désactive également denormals.1

19voto

German Garcia Points 702

Dans gcc, vous pouvez activer la ZONE franche et DAZ avec ceci:

#include <xmmintrin.h>

#define FTZ 1
#define DAZ 1   

void enableFtzDaz()
{
    int mxcsr = _mm_getcsr ();

    if (FTZ) {
            mxcsr |= (1<<15) | (1<<11);
    }

    if (DAZ) {
            mxcsr |= (1<<6);
    }

    _mm_setcsr (mxcsr);
}

également utiliser gcc commutateurs: -msse -mfpmath=sse

(les crédits correspondant à Carl Hetherington [1])

[1] http://carlh.net/plugins/denormals.php

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