¿Debería usar multiplicación o división?


118

Aquí hay una pregunta tonta y divertida:

Digamos que tenemos que realizar una operación simple donde necesitamos la mitad del valor de una variable. Por lo general, hay dos formas de hacer esto:

y = x / 2.0;
// or...
y = x * 0.5;

Suponiendo que estamos usando los operadores estándar proporcionados con el idioma, ¿cuál tiene mejor rendimiento?

Supongo que la multiplicación suele ser mejor, así que trato de ceñirme a eso cuando codifico, pero me gustaría confirmarlo.

Aunque personalmente estoy interesado en la respuesta para Python 2.4-2.5, ¡no dude en publicar una respuesta para otros idiomas! Y si lo desea, no dude en publicar otras formas más sofisticadas (como usar operadores de desplazamiento bit a bit) también.


5
¿Ejecutó un punto de referencia? Es solo una docena de líneas de código. ¿Qué aprendió al ejecutar un punto de referencia? [Sugerencia: hacer eso hubiera sido más rápido que publicar la pregunta aquí.]
S.Lott

4
Gran pregunta, que ha generado algunas respuestas / discusiones bastante interesantes. Gracias :)
stealthcopter

22
Incluso si hubiera aprendido la respuesta comparándola, sigue siendo una pregunta útil y ha generado algunas respuestas interesantes y útiles. También me gustaría que la gente se mantuviera firme y se abstuviera de escribir respuestas y comentarios a las respuestas ofreciendo consejos irrelevantes sobre si vale la pena o no hacer la optimización en cuestión. ¿Por qué no asumir que el OP hace la pregunta tal como está escrita en lugar de asumir que él o ella "realmente" quiere un consejo sobre una reescritura a mayor escala?
Kevin Whitefoot

1
La división es mucho más lenta que la multiplicación. Pero algunos cumplidores / VM inteligentes transforman la división en multiplicación, por lo que sus pruebas tendrán los mismos resultados (ambas pruebas prueban la multiplicación).
Ivan Kuckir

4
Un poco fuera de tema, pero solo quiero decir cuánto estoy de acuerdo con @KevinWhitefoot. No hay nada tan frustrante como leer de los sermonizadores en lugar de respuestas técnicas extrañas a preguntas técnicas. ¡Gracias Kevin por tu comentario!
Jean-François

Respuestas:


78

Pitón:

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real    0m26.676s
user    0m25.154s
sys     0m0.076s

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real    0m17.932s
user    0m16.481s
sys     0m0.048s

la multiplicación es 33% más rápida

Lua:

time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m7.956s
user    0m7.332s
sys     0m0.032s

time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m7.997s
user    0m7.516s
sys     0m0.036s

=> ninguna diferencia real

LuaJIT:

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m1.921s
user    0m1.668s
sys     0m0.004s

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m1.843s
user    0m1.676s
sys     0m0.000s

=> es solo un 5% más rápido

Conclusiones: en Python es más rápido multiplicar que dividir, pero a medida que te acercas a la CPU utilizando máquinas virtuales o JIT más avanzadas, la ventaja desaparece. Es muy posible que una futura máquina virtual de Python lo haga irrelevante


¡Gracias por el consejo sobre el uso del comando de tiempo para la evaluación comparativa!
Edmundito

2
Tu conclusión es incorrecta. Se vuelve más relevante a medida que mejora el JIT / VM. La división se vuelve más lenta en comparación con la menor sobrecarga de la VM. Recuerde que los compiladores generalmente no pueden optimizar mucho el punto flotante para garantizar la precisión.
rasmus

7
@rasmus: A medida que el JIT mejora, es más probable que use una instrucción de multiplicación de CPU aunque haya solicitado la división.
Ben Voigt

68

Utilice siempre lo que sea más claro. Cualquier otra cosa que haga es intentar burlar al compilador. Si el compilador es inteligente, hará todo lo posible para optimizar el resultado, pero nada puede hacer que el próximo chico no te odie por tu pésima solución de desplazamiento de bits (me encanta la manipulación de bits, por cierto, es divertido. ¡Pero divertido! = Legible )

La optimización prematura es la fuente de todos los males. ¡Recuerde siempre las tres reglas de optimización!

  1. No optimices.
  2. Si es un experto, consulte la regla n. ° 1
  3. Si es un experto y puede justificar la necesidad, utilice el siguiente procedimiento:

    • Codifíquelo sin optimizar
    • Determine qué tan rápido es "suficientemente rápido": tenga en cuenta qué requisito / historia del usuario requiere esa métrica.
    • Escribe una prueba de velocidad
    • Pruebe el código existente: si es lo suficientemente rápido, ya está.
    • Recodificarlo optimizado
    • Pruebe el código optimizado. SI no cumple con la métrica, deséchelo y conserve el original.
    • Si cumple con la prueba, mantenga el código original como comentarios

Además, hacer cosas como eliminar bucles internos cuando no son necesarios o elegir una lista vinculada sobre una matriz para una ordenación de inserción no son optimizaciones, solo programación.


7
esa no es la cita completa de Knuth; ver en.wikipedia.org/wiki/…
Jason S

No, hay alrededor de 40 citas diferentes sobre el tema de tantas fuentes diferentes. Unas algunas juntas.
Bill K

Su última oración deja poco claro cuándo aplicar las reglas n. ° 1 y n. ° 2, dejándonos atrás donde comenzamos: debemos decidir qué optimizaciones valen la pena y cuáles no. Fingir que la respuesta es obvia no es una respuesta.
Matt

2
¿Es realmente tan confuso para ti? Aplique siempre las reglas 1 y 2 a menos que en realidad no cumpla con las especificaciones del cliente y esté muy familiarizado con todo el sistema, incluido el idioma y las características de almacenamiento en caché de la CPU. En ese momento, SÓLO siga el procedimiento en 3, no piense simplemente "Oye, si guardo en caché esta variable localmente en lugar de llamar a un captador, las cosas probablemente serán más rápidas. Primero, demuestre que no es lo suficientemente rápido, luego pruebe cada optimización por separado y expulsar a los que no lo hacen en gran medida ayuda de documentos a lo largo del camino..
Bill K

49

Creo que esto se está volviendo tan delicado que sería mejor que hicieras lo que sea que haga que el código sea más legible. A menos que realice las operaciones miles, si no millones, de veces, dudo que alguien alguna vez note la diferencia.

Si realmente tiene que tomar una decisión, la evaluación comparativa es el único camino a seguir. Encuentre qué funciones le están dando problemas, luego averigüe en qué parte de la función ocurren los problemas y corrija esas secciones. Sin embargo, todavía dudo que una sola operación matemática (incluso una repetida muchas, muchas veces) sea la causa de cualquier cuello de botella.


1
Cuando solía hacer procesadores de radar, una sola operación marcaba la diferencia. Pero estábamos optimizando manualmente el código de la máquina para lograr un rendimiento en tiempo real. Por todo lo demás, voto por lo simple y obvio.
S.Lott

Supongo que para algunas cosas, es posible que le importe una sola operación. Pero esperaría que en el 99% de las aplicaciones, no importa.
Thomas Owens

27
Especialmente porque el OP estaba buscando una respuesta en Python. Dudo que cualquier cosa que necesite esa cantidad de eficiencia esté escrita en Python.
Ed S.

4
Una división es probablemente la operación más cara en una rutina de intersección de triángulos, que es la base para la mayoría de trazadores de rayos. Si almacena el recíproco y multiplica en lugar de dividir, experimentará una aceleración muchas veces.
solinente

@solinent - sí, una aceleración, pero dudo "muchas veces" - la división de punto flotante y la multiplicación no deberían ser diferentes en más de 4: 1, a menos que el procesador en cuestión esté realmente optimizado para la multiplicación y no la división.
Jason S

39

La multiplicación es más rápida, la división es más precisa. Perderás algo de precisión si tu número no es una potencia de 2:

y = x / 3.0;
y = x * 0.333333;  // how many 3's should there be, and how will the compiler round?

Incluso si deja que el compilador averigüe la constante invertida con precisión perfecta, la respuesta puede ser diferente.

x = 100.0;
x / 3.0 == x * (1.0/3.0)  // is false in the test I just performed

Es probable que el problema de la velocidad solo importe en los lenguajes C / C ++ o JIT, e incluso entonces solo si la operación está en un bucle en un cuello de botella.


La división es precisa si divide entre números enteros.
zócalo

7
La división de coma flotante con denominador> numerador debe introducir valores sin sentido en los bits de orden inferior; la división suele reducir la precisión.
S.Lott

8
@ S.Lott: No, eso no es cierto. Todas las implementaciones de punto flotante que cumplen con IEEE-754 deben redondear los resultados de cada operación a la perfección (es decir, al número de punto flotante más cercano) con respecto al modo de redondeo actual. Multiplicar por el recíproco siempre va a introducir más error, al menos porque debe ocurrir un redondeo más.
Electro

1
Sé que esta respuesta tiene más de 8 años, pero es engañosa; puede realizar la división sin una pérdida significativa de precisión: y = x * (1.0/3.0);y el compilador generalmente calculará 1/3 en tiempo de compilación. Sí, 1/3 no se puede representar perfectamente en IEEE-754, pero cuando está realizando aritmética de punto flotante está perdiendo precisión de todos modos , ya sea que esté haciendo multiplicación o división, porque los bits de orden inferior están redondeados. Si sabe que su cálculo es tan sensible al error de redondeo, también debe saber cómo abordar mejor el problema.
Jason S

1
@JasonS Acabo de dejar un programa ejecutándose durante la noche, comenzando en 1.0 y contando 1 ULP; Comparé el resultado de multiplicar por (1.0/3.0)con dividir por 3.0. Llegué a 1.0000036666774155, y en ese espacio el 7.3% de los resultados fueron diferentes. Supongo que solo eran diferentes en 1 bit, pero dado que la aritmética IEEE está garantizada para redondear al resultado correcto más cercano, mantengo mi afirmación de que la división es más precisa. Si la diferencia es significativa depende de usted.
Mark Ransom

25

Si desea optimizar su código pero aún así ser claro, intente esto:

y = x * (1.0 / 2.0);

El compilador debería poder dividir en tiempo de compilación, por lo que obtendrá una multiplicación en tiempo de ejecución. Espero que la precisión sea la misma que en el y = x / 2.0caso.

Donde esto puede importar MUCHO es en procesadores integrados donde se requiere emulación de punto flotante para calcular aritmética de punto flotante.


12
Haga lo que quiera (y quienquiera que haya hecho esto): es una práctica estándar en el mundo integrado y los ingenieros de software en ese campo lo encuentran claro.
Jason S

4
+1 por ser el único aquí que se da cuenta de que los compiladores no pueden optimizar las operaciones de punto flotante como quieran. Ni siquiera pueden cambiar el orden de los operandos en una multiplicación para garantizar la precisión (a menos que use un modo relajado).
rasmus

1
Dios mío, hay al menos 6 programadores que piensan que las matemáticas elementales no están claras. AFAIK, la multiplicación IEEE 754 es conmutativa (pero no asociativa).
maaartinus

13
Quizás te estás perdiendo el punto. No tiene nada que ver con la corrección algebraica. En un mundo ideal, debería poder dividir por dos:, y = x / 2.0;pero en el mundo real, es posible que deba engatusar al compilador para que realice una multiplicación menos costosa. Tal vez esté menos claro por qué y = x * (1.0 / 2.0);es mejor, y sería más claro y = x * 0.5;decirlo. Pero cambie el 2.0por 7.0ay preferiría ver y = x * (1.0 / 7.0);que y = x * 0.142857142857;.
Jason S

3
Esto realmente deja en claro por qué es más legible (y preciso) usar su método.
Juan Martinez

21

Solo voy a agregar algo para la opción "otros idiomas".
C: Dado que este es solo un ejercicio académico que realmente no hace ninguna diferencia, pensé en contribuir con algo diferente.

Compilé para ensamblar sin optimizaciones y miré el resultado.
El código:

int main() {

    volatile int a;
    volatile int b;

    asm("## 5/2\n");
    a = 5;
    a = a / 2;

    asm("## 5*0.5");
    b = 5;
    b = b * 0.5;

    asm("## done");

    return a + b;

}

compilado con gcc tdiv.c -O1 -o tdiv.s -S

la división por 2:

movl    $5, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, %edx
shrl    $31, %edx
addl    %edx, %eax
sarl    %eax
movl    %eax, -4(%ebp)

y la multiplicación por 0,5:

movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   LC0
fnstcw  -10(%ebp)
movzwl  -10(%ebp), %eax
orw $3072, %ax
movw    %ax, -12(%ebp)
fldcw   -12(%ebp)
fistpl  -16(%ebp)
fldcw   -10(%ebp)
movl    -16(%ebp), %eax
movl    %eax, -8(%ebp)

Sin embargo, cuando cambié esos intsa doubles (que es lo que probablemente haría Python), obtuve esto:

división:

flds    LC0
fstl    -8(%ebp)
fldl    -8(%ebp)
flds    LC1
fmul    %st, %st(1)
fxch    %st(1)
fstpl   -8(%ebp)
fxch    %st(1)

multiplicación:

fstpl   -16(%ebp)
fldl    -16(%ebp)
fmulp   %st, %st(1)
fstpl   -16(%ebp)

No he comparado ninguno de este código, pero con solo examinar el código puede ver que al usar números enteros, la división por 2 es más corta que la multiplicación por 2. Al usar dobles, la multiplicación es más corta porque el compilador usa los códigos de operación de punto flotante del procesador, que probablemente corran más rápido (pero en realidad no lo sé) que no usarlos para la misma operación. Entonces, en última instancia, esta respuesta ha demostrado que el rendimiento de la multiplacción por 0.5 frente a la división por 2 depende de la implementación del lenguaje y la plataforma en la que se ejecuta. En última instancia, la diferencia es insignificante y es algo de lo que prácticamente nunca debería preocuparse, excepto en términos de legibilidad.

Como nota al margen, puedes ver que en mi programa main()regresa a + b. Cuando elimino la palabra clave volátil, nunca adivinará cómo se ve el ensamblaje (excluyendo la configuración del programa):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

¡Hizo tanto la división, la multiplicación y la suma en una sola instrucción! Claramente, no tiene que preocuparse por esto si el optimizador es respetable.

Perdón por la respuesta demasiado larga.


1
No es una "instrucción única". Simplemente se dobla constantemente.
kvanberendonck

5
@kvanberendonck Por supuesto que es una sola instrucción. Cuéntelos: movl $5, %eax el nombre de la optimización no es importante ni relevante. Solo quería ser condescendiente con una respuesta de cuatro años.
Carson Myers

2
Aún es importante comprender la naturaleza de la optimización, porque es sensible al contexto: solo se aplica si está agregando / multiplicando / dividiendo / etc. constantes de tiempo de compilación, donde el compilador puede hacer todas las matemáticas por adelantado y mover la respuesta final a un registro en tiempo de ejecución. La división es mucho más lenta que la multiplicación en el caso general (divisores de tiempo de ejecución), pero supongo que multiplicar por recíprocos solo ayuda si de otra manera dividirías por el mismo denominador más de una vez de todos modos. Probablemente ya sepas todo eso, pero los programadores más nuevos pueden necesitarlo detallado, así que ... por si acaso.
Mike S

10

En primer lugar, a menos que esté trabajando en C o ASSEMBLY, probablemente esté en un lenguaje de nivel superior donde la memoria se atasca y los gastos generales de llamadas empequeñecerán absolutamente la diferencia entre multiplicar y dividir hasta el punto de la irrelevancia. Entonces, elija lo que se lea mejor en ese caso.

Si habla desde un nivel muy alto, no será mucho más lento para cualquier cosa para la que probablemente lo use. Verá en otras respuestas, las personas necesitan hacer un millón de multiplicar / dividir solo para medir una diferencia de menos de milisegundos entre los dos.

Si todavía tiene curiosidad, desde un punto de vista de optimización de bajo nivel:

El dividendo tiende a tener una tubería significativamente más larga que la multiplicación. Esto significa que se necesita más tiempo para obtener el resultado, pero si puede mantener el procesador ocupado con tareas no dependientes, entonces no terminará costando más que una multiplicación.

La duración de la diferencia de canalización depende completamente del hardware. El último hardware que utilicé fue algo así como 9 ciclos para una multiplicación de FPU y 50 ciclos para una división de FPU. Suena mucho, pero luego perdería 1000 ciclos por una falta de memoria, por lo que puede poner las cosas en perspectiva.

Una analogía es poner un pastel en el microondas mientras miras un programa de televisión. El tiempo total que te alejó del programa de televisión es cuánto tiempo fue ponerlo en el microondas y sacarlo del microondas. El resto del tiempo seguía viendo el programa de televisión. Entonces, si el pastel tardó 10 minutos en cocinarse en lugar de 1 minuto, en realidad no consumió más tiempo de ver televisión.

En la práctica, si va a llegar al nivel de preocuparse por la diferencia entre Multiplicar y Dividir, debe comprender las canalizaciones, la memoria caché, las paradas de rama, la predicción fuera de orden y las dependencias de las canalizaciones. Si esto no suena como lo que pretendías ir con esta pregunta, entonces la respuesta correcta es ignorar la diferencia entre las dos.

Hace muchos (muchos) años era absolutamente crítico evitar las divisiones y usar siempre multiplicaciones, pero en ese entonces los golpes de memoria eran menos relevantes y las divisiones eran mucho peores. En estos días, califico la legibilidad más alto, pero si no hay diferencia de legibilidad, creo que es un buen hábito optar por multiplicar.


7

Escriba lo que indique más claramente su intención.

Después de que su programa funcione, averigüe qué es lento y hágalo más rápido.

No lo hagas al revés.


6

Haz lo que necesites. Piense primero en su lector, no se preocupe por el rendimiento hasta que esté seguro de que tiene un problema de rendimiento.

Deje que el compilador haga el rendimiento por usted.


5

Si está trabajando con enteros o tipos de punto no flotante, no olvide sus operadores de desplazamiento de bits: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);

7
esta optimización se realiza automáticamente entre bastidores en cualquier compilador moderno.
Dustin Getz

¿Alguien ha probado si verifica (usando operaciones de bit) si un operando (?) Tiene una versión desplazable para usarla en su lugar? función mul (a, b) {si (b es 2) return a << 1; si (b es 4) devuelve un << 2; // ... etc return a * b; } Supongo que el IF es tan caro que sería menos eficiente.
Christopher Lightfoot

Eso no se imprimió ni cerca de lo que imaginaba; No importa.
Christopher Lightfoot

Para operaciones constantes, un compilador normal debería hacer el trabajo; pero aquí estamos usando Python, así que no estoy seguro de si es lo suficientemente inteligente como para saberlo. (Debería ser).
Christopher Lightfoot

Buen atajo, excepto que no está claro de inmediato lo que realmente está sucediendo. La mayoría de los programadores ni siquiera reconocen los operadores de desplazamiento de bits.
Blazemonger

4

En realidad, hay una buena razón por la que, como regla general, la multiplicación será más rápida que la división. La división de punto flotante en hardware se realiza con algoritmos de sustracción condicional y de desplazamiento ("división larga" con números binarios) o, más probablemente en estos días, con iteraciones como el algoritmo de Goldschmidt . Desplazar y restar necesita al menos un ciclo por bit de precisión (las iteraciones son casi imposibles de paralelizar al igual que el desplazamiento y suma de la multiplicación), y los algoritmos iterativos hacen al menos una multiplicación por iteración. En cualquier caso, es muy probable que la división requiera más ciclos. Por supuesto, esto no tiene en cuenta las peculiaridades de los compiladores, el movimiento de datos o la precisión. Sin embargo, en general, si está codificando un bucle interno en una parte sensible al tiempo de un programa, escribir 0.5 * xo 1.0/2.0 * xmás bien x / 2.0es algo razonable. La pedantería de "codificar lo que es más claro" es absolutamente cierta, pero los tres son tan cercanos en legibilidad que la pedantería en este caso es simplemente pedante.


3

Siempre he aprendido que la multiplicación es más eficiente.


"eficiente" es la palabra incorrecta. Es cierto que la mayoría de los procesadores se multiplican más rápido de lo que se dividen. Sin embargo, con las modernas arquitecturas canalizadas, es posible que su programa no vea ninguna diferencia. Como muchos otros están diciendo, usted está realmente manera mejor simplemente hacer lo que mejor lee a un ser humano.
TED

3

La multiplicación suele ser más rápida, ciertamente nunca más lenta. Sin embargo, si la velocidad no es crítica, escriba el que sea más claro.


2

La división de punto flotante es (generalmente) especialmente lenta, por lo que aunque la multiplicación de punto flotante también es relativamente lenta, probablemente sea más rápida que la división de punto flotante.

Pero estoy más inclinado a responder "realmente no importa", a menos que el perfil haya demostrado que la división es un cuello de botella frente a la multiplicación. Sin embargo, supongo que la elección de la multiplicación frente a la división no tendrá un gran impacto en el rendimiento de su aplicación.


2

Esto se vuelve más una pregunta cuando está programando en ensamblador o quizás C. Me imagino que con la mayoría de los lenguajes modernos esa optimización como esta se está haciendo por mí.


2

Tenga cuidado con "adivinar que la multiplicación suele ser mejor, así que trato de ceñirme a eso cuando codifico".

En el contexto de esta pregunta específica, mejor aquí significa "más rápido". Lo cual no es muy útil.

Pensar en la velocidad puede ser un grave error. Hay profundas implicaciones de error en la forma algebraica específica del cálculo.

Consulte Aritmética de coma flotante con análisis de errores . Consulte Problemas básicos en aritmética de coma flotante y análisis de errores .

Si bien algunos valores de coma flotante son exactos, la mayoría de los valores de coma flotante son una aproximación; son un valor ideal más algún error. Cada operación se aplica al valor ideal y al valor de error.

Los mayores problemas provienen de tratar de manipular dos números casi iguales. Los bits más a la derecha (los bits de error) dominan los resultados.

>>> for i in range(7):
...     a=1/(10.0**i)
...     b=(1/10.0)**i
...     print i, a, b, a-b
... 
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22

En este ejemplo, puede ver que a medida que los valores se vuelven más pequeños, la diferencia entre números casi iguales crea resultados distintos de cero donde la respuesta correcta es cero.


1

Leí en alguna parte que la multiplicación es más eficiente en C / C ++; No tengo idea sobre los idiomas interpretados; la diferencia probablemente sea insignificante debido a todos los demás gastos generales.

A menos que se convierta en un problema, quédese con lo que es más fácil de mantener / legible; odio cuando la gente me dice esto, pero es tan cierto.


1

Sugeriría la multiplicación en general, porque no tienes que gastar los ciclos asegurándote de que tu divisor no sea 0. Esto no se aplica, por supuesto, si tu divisor es una constante.


1

Android Java, perfilado en Samsung GT-S5830

public void Mutiplication()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a *= 0.5f;
    }
}
public void Division()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a /= 2.0f;
    }
}

Resultados?

Multiplications():   time/call: 1524.375 ms
Division():          time/call: 1220.003 ms

La división es aproximadamente un 20% más rápida que la multiplicación (!)


1
Para ser realista, debe probar a = i*0.5, no a *= 0.5. Así es como la mayoría de los programadores utilizarán las operaciones.
Blazemonger

1

Al igual que con las publicaciones n. ° 24 (la multiplicación es más rápida) y n. ° 30, pero a veces ambas son igualmente fáciles de entender:

1*1e-6F;

1/1e6F;

~ Encuentro que ambos son igualmente fáciles de leer y tengo que repetirlos miles de millones de veces. Por eso es útil saber que la multiplicación suele ser más rápida.


1

Hay una diferencia, pero depende del compilador. Al principio, en vs2003 (c ++) no obtuve una diferencia significativa para los tipos dobles (punto flotante de 64 bits). Sin embargo, al ejecutar las pruebas nuevamente en vs2010, detecté una gran diferencia, hasta un factor 4 más rápido para las multiplicaciones. Al rastrear esto, parece que vs2003 y vs2010 generan diferentes códigos de fpu.

En un Pentium 4, 2,8 GHz, frente a 2003:

  • Multiplicación: 8.09
  • División: 7.97

En un Xeon W3530, vs2003:

  • Multiplicación: 4.68
  • División: 4.64

En un Xeon W3530, vs2010:

  • Multiplicación: 5.33
  • División: 21.05

Parece que en vs2003 una división en un bucle (por lo que el divisor se usó varias veces) se tradujo a una multiplicación con el inverso. En vs2010 esta optimización ya no se aplica (supongo que porque hay un resultado ligeramente diferente entre los dos métodos). Tenga en cuenta también que la CPU realiza divisiones más rápido tan pronto como su numerador sea 0.0. No conozco el algoritmo preciso cableado en el chip, pero tal vez dependa del número.

Editar 18-03-2013: la observación de vs2010


Me pregunto si hay alguna razón por la que un compilador no pueda reemplazar, por ejemplo, n/10.0con una expresión de la forma (n * c1 + n * c2). Esperaría que en la mayoría de los procesadores una división tome más tiempo que dos multiplicaciones y una división, y creo que la división por cualquier constante puede producir un resultado correctamente redondeado en todos los casos usando la formulación indicada.
supercat

1

Aquí hay una respuesta tonta y divertida:

x / 2.0 no es equivalente ax * 0.5

Digamos que escribió este método el 22 de octubre de 2008.

double half(double x) => x / 2.0;

Ahora, 10 años después, aprende que puede optimizar este código. Se hace referencia al método en cientos de fórmulas a lo largo de su aplicación. Así que lo cambia y experimenta una notable mejora del rendimiento del 5%.

double half(double x) => x * 0.5;

¿Fue la decisión correcta cambiar el código? En matemáticas, las dos expresiones son equivalentes. En informática, eso no siempre es cierto. Lea Minimizar el efecto de los problemas de precisión para obtener más detalles. Si sus valores calculados se comparan, en algún momento, con otros valores, cambiará el resultado de los casos extremos. P.ej:

double quantize(double x)
{
    if (half(x) > threshold))
        return 1;
    else
        return -1;
}

La conclusión es; una vez que te conformes con cualquiera de los dos, ¡apégate a ello!


1
Downvote? ¿Qué tal un comentario que explique sus pensamientos? Esta respuesta es definitivamente 100% relevante.
l33t

En ciencias de la computación, la multiplicación / división de valores de punto flotante por potencias de 2 no tiene pérdidas, a menos que el valor se desnormalice o se desborde.
Soonts

Dado que el punto flotante no está exento de pérdidas en el momento de la división, realmente no importa si su afirmación es verdadera. Aunque me sorprendería mucho si lo fuera.
l33t

1
“El punto flotante no está exento de pérdidas en el momento de la división” solo cuando está construyendo con un compilador antiguo que emite código x87 obsoleto. En hardware moderno, solo tener una variable flotante / doble no tiene pérdidas, ya sea IEEE 754 de 32 o 64 bits: en.wikipedia.org/wiki/IEEE_754 Debido a la forma en que funciona IEEE 754, cuando divide por 2 o multiplica por 0,5, disminuye el exponente por 1, el resto de los bits (signo + mantisa) no cambian. Y tanto los números 2y 0.5se pueden representar en IEEE 754 exactamente, sin ninguna pérdida de precisión (a diferencia de, por ejemplo , 0.4o 0.1, no pueden).
Soonts

0

Bueno, si asumimos que una operación de suma / resta cuesta 1, entonces multiplique los costos 5 y divida los costos alrededor de 20.


¿De dónde sacaste estos números? ¿experiencia? ¿sensación de la tripa? artículo en Internet? ¿Cómo cambiarían para diferentes tipos de datos?
kroiz

0

Después de una discusión tan larga e interesante, aquí está mi opinión sobre esto: no hay una respuesta final a esta pregunta. Como algunas personas señalaron, depende tanto del hardware (cf piotrk y gast128 ) como del compilador (cf las pruebas de @Javier ). Si la velocidad no es crítica, si su aplicación no necesita procesar en tiempo real una gran cantidad de datos, puede optar por la claridad usando una división, mientras que si la velocidad de procesamiento o la carga del procesador son un problema, la multiplicación podría ser la más segura. Finalmente, a menos que sepa exactamente en qué plataforma se implementará su aplicación, el punto de referencia no tiene sentido. Y para mayor claridad del código, ¡un solo comentario haría el trabajo!


-3

Técnicamente, no existe la división, solo existe la multiplicación por elementos inversos. Por ejemplo, nunca divide por 2, de hecho multiplica por 0,5.

'División' - engañémonos a nosotros mismos que existe por un segundo - siempre es más difícil que la multiplicación porque para 'dividir' xpor yuno primero se necesita calcular el valor y^{-1}tal que y*y^{-1} = 1y luego hacer la multiplicación x*y^{-1}. Si ya lo sabe, y^{-1}entonces no calcularlo ydebe ser una optimización.


3
Lo que ignora por completo la realidad de ambos comandos existentes en el silicio.
NPSF3000

@ NPSF3000 - No lo sigo. Bajo el supuesto de que existen ambas operaciones, simplemente afirma que la operación de división implica implícitamente el cálculo de un inverso multiplicativo y una multiplicación, lo que siempre será más difícil que hacer una sola multiplicación. El silicio es un detalle de implementación.
satnhak

@ BTyler. Si ambos comandos existen en el silicio, y ambos comandos toman el mismo número de ciclos [como cabría esperar], la complejidad de las instrucciones es completamente irrelevante para un punto de vista de rendimiento.
NPSF3000

@ NPSF3000, pero ambos no toman el mismo número de ciclos, ¿verdad? Porque la multiplicación es más rápida.
Satnhak
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.