Ласкаво просимо у світ денормалізованої плаваючої точки ! Вони можуть завдати шкоди продуктивності !!!
Денормальні (або субнормальні) числа є своєрідним хаком для отримання додаткових значень, близьких до нуля, із представлення плаваючої точки. Операції з денормалізованою плаваючою точкою можуть бути в десятки в сотні разів повільнішими, ніж на нормалізованій плаваючій точці. Це пояснюється тим, що багато процесорів не можуть безпосередньо з ними поводитися, і вони повинні вловлювати та вирішувати їх за допомогою мікрокоду.
Якщо ви роздрукуєте цифри після 10 000 ітерацій, ви побачите, що вони перетворилися на різні значення залежно від того , використовується 0
або 0.1
використовується.
Ось тестовий код, складений на 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;
}
Вихід:
#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
Зверніть увагу, як у другому циклі цифри дуже близькі до нуля.
Денормалізовані числа, як правило, рідкісні, і тому більшість процесорів не намагаються впоратися з ними ефективно.
Щоб продемонструвати, що це має все відношення до денормалізованих чисел, якщо ми зведемо денормали до нуля , додавши це до початку коду:
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Тоді версія з 0
уже не на 10 разів повільніше і фактично стає швидшою. (Для цього потрібно скомпілювати код із включеним SSE.)
Це означає, що замість того, щоб використовувати ці дивні більш низькі точності майже нульових значень, ми просто округлимо до нуля.
Часи синхронізації: Core i7 920 при 3,5 ГГц:
// Don't flush denormals to zero.
0.1f: 0.564067
0 : 26.7669
// Flush denormals to zero.
0.1f: 0.587117
0 : 0.341406
Зрештою, це насправді не має нічого спільного з тим, чи це ціле число, або плаваюча точка. 0
Або 0.1f
перетворюється / зберігається в регістрі зовні обох петель. Тож це не впливає на продуктивність.