2010-05-18 11 views
12

Estoy usando una clase basada en registro en C++ para almacenar valores muy pequeños de punto flotante (ya que los valores de lo contrario van más allá del alcance de double). Como estoy realizando una gran cantidad de multiplicaciones, esto tiene el beneficio adicional de convertir las multiplicaciones en sumas.¿Cómo puedo comparar el rendimiento de log() y la división fp en C++?

Sin embargo, en un cierto punto en mi algoritmo, necesito dividir un valor estándar double por un valor integer y hacer un valor basado en el registro *=. He sobrecargado el operador *= para mi clase basada en registro y el valor del lado derecho se convierte primero a un valor basado en registro ejecutando log() y luego se agrega al valor del lado izquierdo. Por lo tanto, las operaciones realmente realizadas son división de coma flotante, log() y suma de coma flotante.

Mi pregunta si sería más rápido convertir primero el denominador en un valor basado en el registro, que reemplazaría la división de coma flotante con resta de punto flotante, produciendo la siguiente cadena de operaciones: dos veces log(), coma flotante resta, suma de punto flotante.

Al final, esto se reduce a si la división de coma flotante es más rápida o más lenta que log(). Sospecho que una respuesta común sería que esto depende del compilador y de la arquitectura, así que diré que utilizo gcc 4.2 de Apple en darwin 10.3.0. Aún así, espero obtener una respuesta con un comentario general sobre la velocidad de estos dos operadores y/o una idea sobre cómo medir la diferencia yo mismo, ya que podría haber más cosas aquí, por ejemplo. ejecutando los constructores que hacen la conversión de tipo, etc.

¡Salud!

+0

Ehh .. Micro-optimización? Ve por el que es más preciso. – kennytm

+0

@KennyTM: sí, definitivamente micro-optimización, pero parece que esto tiene sentido en esta situación. –

+4

Ejecútelo mil millones de veces en un ciclo desenrollado y cíelo, por Dios. Estas cosas no son cuestiones de opinión. –

Respuesta

12

¿Se divide por el mismo número entero varias veces? Si es así, puedes multiplicar por 1./yourInteger, y solo dividir una vez. Eso sería más rápido de lo que sea posible.

En cuanto a su pregunta real, no solo depende del compilador y la arquitectura, sino también de la microarquitectura y de los datos.

En su plataforma particular (darwin/x86), para el hardware actual i5/i7: ~ 24 ciclos para dividir (1), ~ 35 ciclos para log() (2). Sin embargo, debido a que divide solo usa una sola ranura de despacho de instrucciones, el motor de reordenación del hardware puede hacer otros cálculos útiles mientras la división está en vuelo; log() se implementa en software, por el contrario, por lo que hay menos oportunidades para que el procesador eleve otros cómputos a la latencia del logaritmo. Esto significa que en la práctica, divide a menudo será un poco más rápido.

1) A partir de la optimización de Intel Manual

2) Medido llamando log() en un bucle estrecho y el uso de mach_absolute_time() para obtener el tiempo de la pared.

+0

Un muy buen punto para multiplicar por '1./myInteger'. ¡No hubiera pensado en eso, gracias! Actualmente estoy usando un Core 2 Duo 2006 envejecido, pero sus cálculos son una buena idea y exactamente lo que estaba buscando. –

+2

@Ventzi: Saqué mi "viejo núcleo 2 duo" para hacer un poco de tiempo: 35 ciclos para dividir, 47 ciclos para el registro. –

+0

@StephenCanon: ¿Cómo cronometró las instrucciones? –

1

El principal problema con la división es que, aunque es una instrucción única en la mayoría de las CPU modernas, generalmente tiene un alto latency (31 ciclos en PowerPC - no estoy seguro de lo que está en x86). Sin embargo, parte de esta latencia puede ocultarse si tiene otras instrucciones no dependientes que se pueden emitir al mismo tiempo que la división. Por lo tanto, la respuesta dependerá en cierta medida del tipo de combinación de instrucciones y dependencias que tenga en el ciclo que contenga su división (sin mencionar qué CPU está usando).

Habiendo dicho eso, mi intuición es que la división será más rápida que una función de registro en la mayoría de las arquitecturas.

1

Estoy bastante seguro de que hacer un cálculo de registro a través de cualquier algoritmo va a ser bastante más caro que incluso la división FP.

Por supuesto, la única manera de estar seguro es codificarlo y medir el rendimiento del código. Según su descripción, parece que no debería ser demasiado difícil implementar ambas versiones y probarlas una al lado de la otra.

5

En la arquitectura x86, los logaritmos toman mucho más tiempo que las divisiones: 85 ciclos (rendimiento) para FYL2X en comparación con 40 ciclos para FDIV. Me sorprendería si otras arquitecturas son muy diferentes. Ve con la división de coma flotante.

+2

gcc/darwin (que está usando) usa SSE2 para la aritmética de precisión doble, no la unidad x87. 'log' se implementa en el software, y divide utiliza la instrucción' divsd'. –

+0

@Stephen: Gracias por esa información adicional, eso inclinaría la balanza aún más a favor de div. –

+0

@Martin B: En realidad, hace que la comparación sea más igual; 'divsd' toma 24 ciclos en i7, mientras que la implementación de darwin' log' requiere 35 ciclos (ambos tiempos son * throughput *). En hardware más antiguo, son más iguales aún, ya que las microarquitecturas anteriores tienen divisores más lentos, pero las otras operaciones SSE se ejecutan aproximadamente a la misma velocidad. Sin embargo, la división sigue saliendo adelante. –

Cuestiones relacionadas