Добро пожаловать в мир денормализованных чисел с плавающей точкой ! Они могут нанести ущерб производительности !!!
Денормальные (или субнормальные) числа являются своего рода хаком, чтобы получить некоторые дополнительные значения, очень близкие к нулю, из представления с плавающей запятой. Операции с денормализованной плавающей точкой могут быть в десятки и сотни раз медленнее, чем с нормализованной плавающей точкой. Это потому, что многие процессоры не могут обрабатывать их напрямую и должны перехватывать и разрешать их с помощью микрокода.
Если вы распечатаете числа после 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
преобразуется / хранится в регистре снаружи обеих петель. Так что это не влияет на производительность.