2009-07-18 17 views
25

Hace una o dos décadas, valía la pena escribir código numérico para evitar el uso de multiplicaciones y divisiones y usar sumas y restas en su lugar. Un buen ejemplo es utilizar forward differences para evaluar una curva polinómica en lugar de calcular el polinomio directamente.Cuál es la velocidad relativa del punto flotante agregar vs. punto flotante multiplicar

¿Sigue siendo así o las arquitecturas modernas de la computadora han avanzado hasta el punto en que *,/ya no son mucho más lentas que +, -?

Para ser específico, me interesa el código C/C++ compilado que se ejecuta en los chips x86 típicos modernos con hardware flotante a bordo extenso, no un micro pequeño que intenta hacer FP en el software. Me doy cuenta de que la canalización y otras mejoras arquitectónicas impiden recuentos de ciclos específicos, pero me gustaría obtener una intuición útil.

Respuesta

20

También depende de la combinación de instrucciones. Su procesador tendrá varias unidades de cómputo disponibles en cualquier momento, y obtendrá el máximo rendimiento si todas están llenas todo el tiempo. Entonces, ejecutar un bucle de mul es tan rápido como ejecutar un bucle o agregar, pero no ocurre lo mismo si la expresión se vuelve más compleja.

Por ejemplo, tome este bucle:

for(int j=0;j<NUMITER;j++) { 
    for(int i=1;i<NUMEL;i++) { 
    bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; 
    } 
} 

para NUMITER = 10^7, Numel = 10^2, ambas matrices inicializadas a pequeños números positivos (NaN es mucho más lento), esto se lleva a 6,0 segundos utilizando se duplica en un proceso de 64 bits Si sustituyo el lazo con

bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ; 

Sólo se tarda 1,7 segundos ... así ya que "fue la mano" las adiciones, las mul eran esencialmente libre; y la reducción en las adiciones ayudó. Se consigue de más confuso:

bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; 

- misma mul/añadir distribución, pero ahora se añade la constante en lugar de multiplicarse en - toma 3,7 segundos. Es probable que su procesador esté optimizado para realizar cálculos numéricos típicos de manera más eficiente; así que las sumas de mull y las sumas escaladas son casi tan buenas como pueden ser; la adición de constantes no es tan común, por lo que es más lento ...

bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/ 

toma de nuevo 1.7 segundos.

bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/ 

(igual que el ciclo inicial, pero sin la constante adición costosa: 2.1 segundo)

bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/ 

(en su mayoría MULS, pero una adición: 1,9 segundos)

Así que, básicamente; es difícil decir cuál es más rápido, pero si desea evitar los cuellos de botella, lo más importante es tener una mezcla sensata, evitar NaN o INF, evitar agregar constantes. Hagas lo que hagas, asegúrate de probar y probar varias configuraciones del compilador, ya que a menudo los pequeños cambios pueden marcar la diferencia.

Algunos más casos:

bla *= someval; // someval very near 1.0; takes 2.1 seconds 
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds 
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds 
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds 
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds 
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86 
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86 
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86 
+1

La mezcla de instrucciones es un buen punto, tengo personas con las que trabajo que insisten en que un DSP de punto flotante 200 va a realizar un DSP de 600 puntos fijos. No hacen absolutamente ningún procesamiento de bucle cerrado, y pasan más tiempo procesando E/S que realizando calcuaciones. Un procesador de punto fijo más rápido ganaría según la combinación general de instrucciones, pero la gente simplemente piensa que las unidades de FP son mágicas en lugar de una implementación de HW de una estructura de datos. – NoMoreZealots

+0

Ah sí, el appproach mágico ;-) - eso es desafortunado. –

+1

buena explicación con ejemplos intuitivos! –

1

No puedo encontrar una referencia definitiva, pero la gran cantidad de experimentos me dice que la multiplicación de flotación en la actualidad es casi la misma velocidad que la suma y la resta, mientras que la división no (pero tampoco es "muchas veces" más lenta). Puede obtener la intuición que desea solo ejecutando sus propios experimentos: recuerde generar los números aleatorios (millones de ellos) de antemano, léalos antes de comenzar el tiempo y use los contadores de rendimiento de la CPU (sin ningún otro proceso en ejecución, como tanto como puedes detenerlos) para una medición precisa!

-1

Probablemente hay muy poca diferencia en el tiempo entre la multiplicación y la suma. Por otro lado, la división todavía es significativamente más lenta que la multiplicación debido a su naturaleza recursiva. en la arquitectura x86 moderna. Se deben tener en cuenta las instrucciones sse al hacer operaciones de coma flotante en lugar de utilizar el fpu. Aunque un buen compilador C/C++ debería darle la opción de usar sse en lugar de fpu.

1

La diferencia de velocidad de */vs + depende de la arquitectura de su procesador. En general, y con x86 en particular, la diferencia de velocidad se ha reducido con los procesadores modernos. * debe estar cerca de +, en caso de duda: solo experimente. Si tiene un problema realmente difícil con muchas operaciones de FP, también considere usar su GPU (GeForce, ...) que funciona como un procesador vectorial.

7

La mejor manera de responder a esta pregunta es escribir un punto de referencia/perfil del procesamiento que necesita hacer. Empirical debe usarse sobre teórico siempre que sea posible. Especialmente cuando es fácil de alcanzar.

Si ya conoces las diferentes implementaciones de las matemáticas que necesitas hacer, podrías escribir unas cuantas correlaciones de códigos diferentes de las matemáticas y ver dónde alcanza tu máximo rendimiento. Esto permitirá que el procesador/compilador genere diferentes flujos de ejecución para llenar las tuberías del procesador y le dará una respuesta concreta a su respuesta.

Si le interesa específicamente el rendimiento de las instrucciones de tipo DIV/MUL/ADD/SUB, puede incluso lanzar un ensamblaje en línea para controlar específicamente qué variantes de estas instrucciones se ejecutan. Sin embargo, debe asegurarse de mantener ocupadas las unidades de ejecución de múltiples unidades para tener una buena idea del rendimiento que puede lograr el sistema.

También hacer algo así le permitiría comparar el rendimiento en múltiples variaciones del procesador simplemente ejecutando el mismo programa en ellos, y también podría permitirle tener en cuenta las diferencias de la placa base.

Editar:

Arquitectura básica de un + - es idéntica. Entonces lógicamente tardan el mismo tiempo en calcular. * por otro lado, requieren múltiples capas, generalmente construidas a partir de "sumadores completos" para completar una sola operación.Esto garantiza que aunque se puede emitir un * a la tubería en cada ciclo, tendrá una latencia más alta que un circuito de suma/resta. Una operación fp/normalmente se implementa utilizando un método de aproximación que converge iterativamente hacia la respuesta correcta a lo largo del tiempo. Este tipo de aproximaciones se implementan típicamente a través de la multiplicación. Entonces, para el punto flotante generalmente puede suponer que la división tomará más tiempo porque no es práctico "desenrollar" las multiplicaciones (que ya es un circuito grande en y de sí mismo) en la tubería de una multitud de circuitos multiplicadores. Aún así, el rendimiento de un sistema dado se mide mejor a través de pruebas.

16

En teoría, la información está aquí:

Intel®64 and IA-32 Architectures Optimization Reference Manual, APPENDIX C INSTRUCTION LATENCY AND THROUGHPUT

Por cada procesador que la lista, la latencia en FMUL es muy cercana a la de FADD o FDIV. En algunos de los procesadores más antiguos, FDIV es 2-3 veces más lento que eso, mientras que en los procesadores más nuevos, es lo mismo que FMUL.

Advertencias:

  1. El documento he vinculado en realidad dice que no se puede confiar en estos números en la vida real ya que el procesador va a hacer lo que quiere hacer las cosas más rápido si es correcta.

  2. Existe una buena posibilidad de que el compilador decida utilizar uno de los muchos conjuntos de instrucciones más nuevos que tienen una multiplicación/división en coma flotante disponible.

  3. Este es un documento complicado solo para que lo lean los escritores de compiladores y podría haberlo entendido mal. Como no estoy seguro de por qué falta el número de latencia FDIV para algunas de las CPU.

+1

documento muy fresco. Creo que una cosa que permanece constante (y este documento lo muestra) es que la división es mucho más lenta que la multiplicación, la suma y la resta. Desde el aspecto de este documento, la latencia de la división de precisión doble es 10 veces más lenta que la multiplicación. Entonces, por ejemplo, creo que llamar a x = y * 0.5 debería ser más rápido que llamar a x = y/2. –

+0

@SteveWortham ¿Puedes indicar que la información sobre fdiv es 10 veces más lenta que fmul? – 0fnt

+0

@ user247077 - No lo recuerdo. Esto fue hace un par de años. Sin embargo, hay gráficos en este documento que hacen referencia a la latencia de muchos comandos diferentes. Y FMUL es ciertamente más rápido que FDIV en estas tablas. Luego están DIV r64 y MUL r64 en la página C-33, que tienen una gran brecha entre ellos en latencia. El año pasado pude haber tocado estas instrucciones (o un equivalente de AMD) cuando creé una aplicación de 64 bits para comparar la diferencia de rendimiento entre la multiplicación y la división ... http://swortham.blogspot.com/2011/10/how -much-faster-es-multiplication-than.html –

Cuestiones relacionadas