Benvenuti nel mondo del virgola mobile denormalizzato ! Possono rovinare le prestazioni !!!
I numeri denormali (o subnormali) sono una specie di hack per ottenere alcuni valori extra molto vicini allo zero dalla rappresentazione in virgola mobile. Le operazioni su virgola mobile denormalizzate possono essere decine o centinaia di volte più lente rispetto a virgola mobile normalizzata. Questo perché molti processori non possono gestirli direttamente e devono intercettarli e risolverli utilizzando il microcodice.
Se si stampano i numeri dopo 10.000 iterazioni, si noterà che sono stati convertiti in valori diversi a seconda che vengano utilizzati 0
o meno 0.1
.
Ecco il codice di test compilato su 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;
}
Produzione:
#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
Nota come nella seconda serie i numeri sono molto vicini allo zero.
I numeri denormalizzati sono generalmente rari e quindi la maggior parte dei processori non cerca di gestirli in modo efficiente.
Per dimostrare che questo ha tutto a che fare con i numeri denormalizzati, se azzeriamo i denormali aggiungendo questo all'inizio del codice:
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Quindi la versione con 0
non è più 10 volte più lenta e in realtà diventa più veloce. (Ciò richiede che il codice sia compilato con SSE abilitato.)
Ciò significa che invece di utilizzare questi strani valori di precisione quasi zero quasi zero, invece arrotondiamo a zero.
Tempi: Core i7 920 a 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
Alla fine, questo non ha davvero nulla a che fare con il fatto che sia un numero intero o un virgola mobile. L' 0
o 0.1f
viene convertito / memorizzato in un registro esterno a entrambi i loop. Quindi ciò non ha alcun effetto sulle prestazioni.