Selamat datang di dunia floating-point yang didenormalkan ! Mereka dapat mendatangkan malapetaka pada kinerja !!!
Angka yang tidak normal (atau subnormal) adalah jenis peretasan untuk mendapatkan beberapa nilai tambahan yang mendekati nol dari representasi floating point. Operasi pada floating-point terdenormalkan bisa puluhan hingga ratusan kali lebih lambat daripada pada floating-point normal. Ini karena banyak prosesor tidak dapat menanganinya secara langsung dan harus menjebak dan menyelesaikannya menggunakan mikrokode.
Jika Anda mencetak angka-angka setelah 10.000 iterasi, Anda akan melihat bahwa mereka telah konvergen ke nilai yang berbeda tergantung pada apakah 0
atau 0.1
digunakan.
Berikut kode tes yang dikompilasi di 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;
}
Keluaran:
#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
Perhatikan bagaimana pada putaran kedua jumlahnya sangat mendekati nol.
Angka yang didenormalkan umumnya jarang dan karenanya kebanyakan prosesor tidak mencoba menanganinya secara efisien.
Untuk menunjukkan bahwa ini semua ada hubungannya dengan angka-angka yang didenormalkan, jika kita membesar-besarkan denormals ke nol dengan menambahkan ini ke awal kode:
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Kemudian versi dengan 0
tidak lagi 10x lebih lambat dan sebenarnya menjadi lebih cepat. (Ini mengharuskan kode dikompilasi dengan SSE diaktifkan.)
Ini berarti bahwa alih-alih menggunakan nilai presisi hampir nol yang aneh ini, kami hanya membulatkannya ke nol.
Pengaturan waktu: 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
Pada akhirnya, ini benar-benar tidak ada hubungannya dengan apakah itu bilangan bulat atau floating-point. The 0
atau 0.1f
diubah / disimpan menjadi luar daftar dari kedua loop. Sehingga tidak berpengaruh pada kinerja.