Bem-vindo ao mundo do ponto flutuante desnormalizado ! Eles podem causar estragos no desempenho !!!
Números desnormais (ou subnormais) são uma espécie de hack para obter alguns valores extras muito próximos de zero da representação de ponto flutuante. As operações no ponto flutuante desnormalizado podem ser dezenas a centenas de vezes mais lentas que no ponto flutuante normalizado. Isso ocorre porque muitos processadores não podem lidar com eles diretamente e devem interceptá-los e resolvê-los usando o microcódigo.
Se você imprimir os números após 10.000 iterações, verá que eles convergiram para valores diferentes, dependendo de serem 0
ou 0.1
não utilizados.
Aqui está o código de teste compilado em 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;
}
Resultado:
#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
Observe como na segunda execução os números estão muito próximos de zero.
Números desnormalizados são geralmente raros e, portanto, a maioria dos processadores não tenta lidar com eles com eficiência.
Para demonstrar que isso tem tudo a ver com números desnormalizados, se colocarmos desnormalizados em zero , adicionando isso ao início do código:
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Então a versão com 0
não é mais 10x mais lenta e, na verdade, fica mais rápida. (Isso requer que o código seja compilado com o SSE ativado.)
Isso significa que, em vez de usar esses valores esquisitos de baixa precisão quase zero, arredondamos para zero.
Tempos: 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
No final, isso realmente não tem nada a ver com se é um número inteiro ou um ponto flutuante. O 0
ou 0.1f
é convertido / armazenado em um registro fora dos dois loops. Portanto, isso não afeta o desempenho.