огромное прошедшее время для умножения FLT_MIN

Умножения с плавающей запятой, приводящие к результатам, ограниченным швом FLT_MIN, очень медленны по сравнению с другими умножениями с плавающей запятой. Запустив приведенный ниже пример кода на моей машине с Linux, я получил следующие результаты:

Elapsed time for 1E09 iterations of  0 * 0.900000 : 2.623269 s 
Elapsed time for 1E09 iterations of  1.17549e-38 * 0.900000 : 73.851011 s 
Elapsed time for 1E09 iterations of  2.35099e-38 * 0.900000 : 2.637788 s 
Elapsed time for 1E09 iterations of  0.00870937 * 0.900000 : 2.632788 s 
Elapsed time for 1E09 iterations of  1 * 0.900000 :  2.654571 s 
Elapsed time for 1E09 iterations of  3.40282e+38 * 0.900000 : 2.639316 s 

Операция 1.17549e-38 * 0.9 занимает как минимум в 25 раз больше времени, чем другие протестированные операции умножения. Это известная проблема?

В критически важном по времени проекте, в котором необходимо выполнить большое количество таких умножений, потенциально ведущих к FLT_MIN, что может быть быстрым способом обойти эту проблему? (Я не могу позволить себе проверять каждое значение перед его умножением, но я могу допустить ошибку порядка e-5 в результате умножения)

#include <sys/time.h>
#include <stdio.h>
#include <float.h>
#define N_VALS 6
#define ALMOST_MIN FLT_MIN*2
int timeval_subtract (struct timeval *result,struct timeval * start,struct timeval *stop)
{
  long int sdiff= stop-> tv_sec - start->tv_sec;
  long int udiff=stop->tv_usec - start-> tv_usec;
  if (udiff<0)
  {
    udiff=1000000+udiff;
    sdiff--;
  }
  result->tv_sec = sdiff;
  result->tv_usec = udiff;  
}

int main()
{
  float values [N_VALS]={0.0f,FLT_MIN,ALMOST_MIN, 0.00870937f, 1.0f, FLT_MAX};
  float out, mul=0.9f;
  int i, j, err;
  struct timeval t_start, t_stop, t_elaps;
  for (j=0; j<N_VALS; j++)
  {
    err=gettimeofday(&t_start, NULL);
    for (i=0; i<1000000000; i++)
      out=values[j]*mul;

    err=gettimeofday(&t_stop, NULL);
    timeval_subtract(&t_elaps, &t_start, &t_stop);
    printf("Elapsed time for 1E09 iterations of  %g * %f : %ld.%06ld s \n", values[j], mul, t_elaps.tv_sec, t_elaps.tv_usec);
  }
}

person Gianni    schedule 19.01.2014    source источник
comment
Скорее всего, вы сталкиваетесь с субнормальными числами, которые занимают больше времени с помощью аппаратного или программного FP — a известная проблема.   -  person chux - Reinstate Monica    schedule 19.01.2014
comment
Мне было бы интересно узнать производительность, если бы вы использовали double, но ограничили свой диапазон до float. Каким может быть худший случай?   -  person chux - Reinstate Monica    schedule 19.01.2014
comment
какой компилятор вы используете?   -  person Chris J. Kiick    schedule 19.01.2014
comment
gcc и g++ дали похожие результаты   -  person Gianni    schedule 20.01.2014


Ответы (1)


Причина, по которой выполнение .9 * FLT_MIN занимает гораздо больше времени, заключается в том, что результат меньше, чем наименьшее значение, которое может представлять число с плавающей запятой. Это заставляет процессор вызывать исключение, которое обрабатывается ОС и может включать вызов функций в пользовательском пространстве. Это занимает много времени по сравнению с простым умножением с плавающей запятой, которое выполняется полностью аппаратно.

Как это исправить? Зависит от вашей платформы и инструментов сборки. Если вы используете gcc, то он пытается использовать настройки ЦП для оптимизации некоторых операций, в зависимости от того, какие флаги вы установили. Посмотрите руководство gcc для -ffast-math и связанных флагов оптимизации с плавающей запятой. Обратите внимание, что использование этих флагов может привести к результатам, которые не полностью соответствуют спецификации IEEE с плавающей запятой.

person Chris J. Kiick    schedule 19.01.2014
comment
Вы не должны увидеть большого снижения производительности для субнормальных значений на Sandy Bridge или более новых процессорах. -ffast-math устанавливает все сгенерированные субнормальы в ноль. - person tim18; 14.11.2015