¿Por qué floor() es tan lento?


Escribí un código recientemente (ISO/ANSI C), y me sorprendió el pobre rendimiento que logró. En resumen, resultó que el culpable era la función floor(). No solo era lento, sino que no se vectorizaba (con el compilador Intel, también conocido como ICL).

Aquí hay algunos puntos de referencia para realizar el piso para todas las células en una matriz 2D:

VC:  0.10
ICL: 0.20

Compare eso con un molde simple:

VC:  0.04
ICL: 0.04

¿Cómo puede floor() ser mucho más lento que un simple elenco?! Hace esencialmente lo mismo cosa (aparte de los números negativos). 2a pregunta: ¿Alguien sabe de una implementación súper rápida floor()?

PD: Aquí está el bucle que estaba benchmarking:

void Floor(float *matA, int *intA, const int height, const int width, const int width_aligned)
{
    float *rowA=NULL;
    int   *intRowA=NULL;
    int   row, col;

    for(row=0 ; row<height ; ++row){
        rowA = matA + row*width_aligned;
        intRowA = intA + row*width_aligned;
#pragma ivdep
        for(col=0 ; col<width; ++col){
            /*intRowA[col] = floor(rowA[col]);*/
            intRowA[col] = (int)(rowA[col]);
        }
    }
}
Author: Yu Hao, 2009-05-05

6 answers

Un par de cosas hacen que el piso sea más lento que un yeso y evitan la vectorización.

El más importante:

Floor puede modificar el estado global. Si pasa un valor que es demasiado grande para ser representado como un entero en formato flotante, la variable errno se establece en EDOM. También se realiza un manejo especial para NaNs. Todo este comportamiento es para aplicaciones que quieren detectar el caso de desbordamiento y manejar la situación de alguna manera (no me preguntes cuan).

Detectar estas condiciones problemáticas no es sencillo y supone más del 90% del tiempo de ejecución de floor. El real redondeo es barato y podría estar en línea/vectorizados. También es una gran cantidad de código, por lo que incrustar toda la función de piso haría que su programa se ejecute más lento.

Algunos compiladores tienen indicadores de compilador especiales que permiten al compilador optimizar algunas de las reglas c-standard raramente usadas. Por ejemplo GCC se le puede decir que no está interesado en errno en absoluto. Para hacerlo pasa -fno-math-errno o -ffast-math. ICC y VC pueden tener indicadores de compilador similares.

Por cierto - Puede rodar su propia función de piso usando moldes simples. Solo tienes que manejar los casos negativos y positivos de manera diferente. Eso puede ser mucho más rápido si no necesita el manejo especial de desbordamientos y NaNs.

 40
Author: Nils Pipenbrinck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-08 21:21:32

Si vas a convertir el resultado de la operación floor() a un int, y si no te preocupa el desbordamiento, entonces el siguiente código es mucho más rápido que (int)floor(x):

inline int int_floor(double x)
{
  int i = (int)x; /* truncate */
  return i - ( i > x ); /* convert trunc to floor */
}
 17
Author: dgobbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-25 11:32:02

Piso y techo sin ramas (mejor utilizar la pipilina) sin comprobación de error

int f(double x)
{
    return (int) x - (x < (int) x); // as dgobbi above, needs less than for floor
}

int c(double x)
{
    return (int) x + (x > (int) x);
}

O utilizando el suelo

int c(double x)
{
    return -(f(-x));
}
 10
Author: PolarBear2015,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-24 05:07:43

Sí, floor() es extremadamente lento en todas las plataformas, ya que tiene que implementar una gran cantidad de comportamiento de la especificación fp de IEEE. Realmente no se puede usar en bucles internos.

A veces uso una macro para aproximar floor ():

#define PSEUDO_FLOOR( V ) ((V) >= 0 ? (int)(V) : (int)((V) - 1))

No se comporta exactamente como floor(): por ejemplo, floor(-1) == -1 pero PSEUDO_FLOOR(-1) == -2, pero es lo suficientemente cerca para la mayoría de los usos.

 1
Author: jrgc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-25 11:32:58
  1. No hacen lo mismo. floor() es una función. Por lo tanto, usarlo incurre en una llamada a función, asignando un marco de pila, copiando parámetros y recuperando el resultado. Casting no es una llamada a una función, por lo que utiliza mecanismos más rápidos (creo que puede usar registros para procesar los valores).
  2. Probablemente floor() ya está optimizado.
  3. ¿Puedes exprimir más rendimiento de tu algoritmo? Tal vez cambiar filas y columnas puede ayudar? ¿Puede almacenar valores comunes en caché? ¿Están activadas todas las optimizaciones de su compilador? ¿Puedes cambiar un sistema operativo? un compilador? Perlas de programación de Jon Bentley tiene una gran revisión de posibles optimizaciones.
 -1
Author: Yuval F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-05 10:00:55

Ronda doble rápida

double round(double x)
{
    return double((x>=0.5)?(int(x)+1):int(x));
}

Registro de terminal

Prueba custom_1 8.3837

Test native_1 18.4989

Prueba custom_2 8.36333

Test native_2 18.5001

Prueba custom_3 8.37316

Test native_3 18.5012


Prueba

void test(char* name, double (*f)(double))
{
    int it = std::numeric_limits<int>::max();

    clock_t begin = clock();

    for(int i=0; i<it; i++)
    {
        f(double(i)/1000.0);
    }
    clock_t end = clock();

    cout << "test " << name << " " << double(end - begin) / CLOCKS_PER_SEC << endl;

}

int main(int argc, char **argv)
{

    test("custom_1",round);
    test("native_1",std::round);
    test("custom_2",round);
    test("native_2",std::round);
    test("custom_3",round);
    test("native_3",std::round);
    return 0;
}

Resultado

El tipo de fundición y el uso de su cerebro es ~3 veces más rápido que el uso de funciones nativas.

 -2
Author: Jan Cajthaml,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-25 11:22:23