¿Cuál de las siguientes técnicas es la mejor opción para dividir un número entero entre 2 y por qué?
Técnica 1:
x = x >> 1;
Técnica 2:
x = x / 2;
Aquí x
hay un número entero.
c++
c
optimization
division
micro-optimization
Abhineet
fuente
fuente
x
nuevamente, ninguno de los dos es apropiado de esta manera: debería ser cualquierax >>= 1
ox /= 2
, dependiendo de lo que pretenda expresar con la operación. No porque sea más rápido (cualquier compilador moderno compilará todas las variantes equivalentes a un ensamblaje idéntico y rápido de todos modos) sino porque es menos confuso.x = x >>> 1
. También tenga en cuenta que, dependiendo de la plataforma y el compilador, puede ser bastante razonable optimizar manualmente las divisiones y multiplicaciones usando turnos. - Pensando en microcontroladores, por ejemplo, sin soporte directo de ALU para la multiplicación.x /= 2
porque sex >>= 1
parece demasiado a la unión monádica;)x = x / 2
lugar de hacerlox /= 2
. Preferencia subjetiva quizás :)⬜=
combinaciones, estas deberían usarse siempre que sea posible. Elimina el ruido y pone énfasis en el hecho de quex
se modifica , mientras que el=
operador general sugiere que adquiere un valor completamente nuevo independiente del anterior. - Siempre evitando los operadores combinados (para que sea legible por lo que alguien que sólo sabe operadores matemáticos) puede tener su punto así, pero entonces que había necesidad de renunciar a la extremadamente útil++
,--
,+=
, también.Respuestas:
Use la operación que mejor describa lo que está tratando de hacer.
Tenga en cuenta que no son exactamente equivalentes. Pueden dar diferentes resultados para enteros negativos. Por ejemplo:
(ideona)
fuente
%
y/
deben ser consistentes para los operandos positivos y negativos, de modo que(a/b)*b+(a%b)==a
sea cierto independientemente de los signos dea
yb
. Por lo general, el autor tomará decisiones para obtener el mejor rendimiento posible de la CPU.¿El primero parece dividir? No. Si quieres dividir, úsalo
x / 2
. El compilador puede optimizarlo para usar bit-shift si es posible (se llama reducción de fuerza), lo que lo convierte en una microoptimización inútil si lo hace por su cuenta.fuente
Para agregar: hay muchas razones para favorecer el uso.
x = x / 2;
Aquí hay algunas:expresa su intención más claramente (suponiendo que no esté tratando con bits de registro de giro de bits o algo así)
el compilador reducirá esto a una operación de turno de todos modos
incluso si el compilador no lo redujo y eligió una operación más lenta que el cambio, la probabilidad de que esto termine afectando el rendimiento de su programa de una manera mensurable es en sí misma muy pequeña (y si lo afecta de manera medible, entonces tiene un valor real razón para usar un turno)
Si la división va a ser parte de una expresión más grande, es más probable que obtenga la prioridad correcta si usa el operador de división:
la aritmética firmada podría complicar las cosas aún más que el problema de precedencia mencionado anteriormente
para reiterar: el compilador ya lo hará por usted de todos modos. De hecho, convertirá la división por una constante en una serie de cambios, sumas y multiplicaciones para todo tipo de números, no solo potencias de dos. Vea esta pregunta para obtener enlaces a más información sobre esto.
En resumen, no compra nada codificando un turno cuando realmente quiere multiplicar o dividir, excepto tal vez una mayor posibilidad de introducir un error. Ha pasado toda una vida desde que los compiladores no eran lo suficientemente inteligentes como para optimizar este tipo de cosas a un cambio cuando era apropiado.
fuente
a/b/c*d
(donde sea..d
denotaban las variables numéricas) en lugar de la mucho más legible(a*d)/(b*c)
.a*d
ob*c
produciría un desbordamiento, la forma menos legible no es equivalente y tiene una ventaja evidente. PD Estoy de acuerdo en que los paréntesis son tu mejor amigo.a/b/c*d
código R, en un contexto donde el desbordamiento significaría que algo estaba mal con los datos, y no en, digamos, un bloque crítico de rendimiento del código C).x=x/2;
es solo "más claro" quex>>=1
six
nunca será un número negativo impar o si a uno no le importan los errores ocasionales. De lo contrariox=x/2;
yx>>=1;
tienen diferentes significados. Si lo que uno necesita es el valor calculado porx>>=1
, lo consideraría más claro quex = (x & ~1)/2
ox = (x < 0) ? (x-1)/2 : x/2
, o cualquier otra formulación que pueda pensar en usar la división por dos. Del mismo modo, si uno necesita el valor calculado porx/=2
, eso es más claro que((x + ((unsigned)x>>31)>>1)
.Depende de lo que entiendas por mejor .
Si quieres que tus colegas te odien, o que tu código sea difícil de leer, definitivamente elegiría la primera opción.
Si quieres dividir un número entre 2, ve con el segundo.
Los dos no son equivalentes, no se comportan igual si el número es negativo o dentro de expresiones más grandes: el desplazamiento de bits tiene menor prioridad que
+
o-
, la división tiene mayor prioridad.Debe escribir su código para expresar cuál es su intención. Si le preocupa el rendimiento, no se preocupe, el optimizador hace un buen trabajo en este tipo de micro optimizaciones.
fuente
Simplemente use divide (
/
), suponiendo que sea más claro. El compilador se optimizará en consecuencia.fuente
ASSUME(x >= 0); x /= 2;
terminadox >>= 1;
, pero ese sigue siendo un punto importante para mencionar.Estoy de acuerdo con otras respuestas que debe favorecer
x / 2
porque su intención es más clara y el compilador debe optimizarlo para usted.Sin embargo, otra razón para preferir
x / 2
másx >> 1
es que el comportamiento de>>
depende de la implementación, six
es firmado,int
y es negativo.De la sección 6.5.7, viñeta 5 del estándar ISO C99:
fuente
x>>scalepower
los números negativos será precisamente lo que se necesita al dividir un valor por una potencia de dos para fines tales como el renderizado de pantalla, mientras que el usox/scalefactor
será incorrecto a menos que uno aplique correcciones a valores negativos.x / 2
es más claro yx >> 1
no es mucho más rápido (según un micro-benchmark, aproximadamente un 30% más rápido para una JVM de Java). Como otros han señalado, para los números negativos, el redondeo es ligeramente diferente, por lo que debe tener esto en cuenta cuando desee procesar números negativos. Algunos compiladores pueden convertir automáticamentex / 2
ax >> 1
si conocen el número no puede ser negativo (incluso pensó que no podía verificar esto).Incluso
x / 2
puede no usar la instrucción de CPU de división (lenta), porque algunos atajos son posibles , pero aún es más lento quex >> 1
.(Esta es una C / C ++ cuestión, otros lenguajes de programación tienen más operadores. Para Java también es el desplazamiento sin signo de la derecha,
x >>> 1
que es de nuevo diferente. Permite calcular correctamente el valor medio (promedio) de dos valores, por lo que(a + b) >>> 1
la voluntad devuelve el valor medio incluso para valores muy grandes dea
yb
. Esto es necesario, por ejemplo, para la búsqueda binaria si los índices de la matriz pueden ser muy grandes. Hubo un error en muchas versiones de la búsqueda binaria , porque solían(a + b) / 2
calcular el promedio. no funciona correctamente. La solución correcta es usar(a + b) >>> 1
en su lugar.)fuente
x/2
ax>>1
en los casos en quex
pueden ser negativas. Si lo que uno quiere es el valor quex>>1
calcularía, seguramente será más rápido que cualquier expresiónx/2
que implique el mismo valor.x/2
ax>>1
si sabe que el valor no es negativo. Intentaré actualizar mi respuesta.div
embargo, los compiladores aún evitan una instrucción al convertirx/2
a(x + (x<0?1:0)) >> 1
(donde >> es un desplazamiento aritmético a la derecha, que se desplaza en bits de signo). Esto requiere 4 instrucciones: copie el valor, shr (para obtener solo el bit de signo en un registro), agregue, sar. goo.gl/4F8Ms4Knuth dijo:
Entonces sugiero usar
x /= 2;
De esta manera, el código es fácil de entender y también creo que la optimización de esta operación en esa forma, no significa una gran diferencia para el procesador.
fuente
(n+8)>>4
funciona bien. ¿Puede ofrecer algún enfoque que sea tan claro o eficiente sin utilizar un desplazamiento a la derecha firmado?Echa un vistazo a la salida del compilador para ayudarte a decidir.
Ejecuté esta prueba en x86-64 con gcc (GCC) 4.2.1 20070719 [FreeBSD]
También vea las salidas del compilador en línea en godbolt .
Lo que ve es que el compilador utiliza una
sarl
instrucción (aritmética de desplazamiento a la derecha) en ambos casos, por lo que reconoce la similitud entre las dos expresiones. Si usa la división, el compilador también necesita ajustar los números negativos. Para hacerlo, desplaza el bit de signo hacia abajo al bit de orden más bajo y lo agrega al resultado. Esto soluciona el problema de uno por uno al cambiar los números negativos, en comparación con lo que haría una división.Dado que el caso de división hace 2 turnos, mientras que el caso de cambio explícito solo hace uno, ahora podemos explicar algunas de las diferencias de rendimiento medidas por otras respuestas aquí.
Código C con salida de ensamblaje:
Para dividir, su entrada sería
y esto compila a
de manera similar para turno
con salida:
fuente
d
. Dicha partición es útil para muchos propósitos. Incluso si uno prefiere tener el punto de interrupción en otro lugar que no sea entre 0 y -1, agregar un desplazamiento lo moverá. Una división entera que satisfaga el segundo axioma dividirá la recta numérica en regiones que son principalmente de tamañod
, pero una de las cuales es de tamaño2*d-1
. No exactamente divisiones "iguales". ¿Puedes ofrecer sugerencias de cuándo la partición de Oddball es realmente útil?sar
). goo.gl/KRgIkb . Esta publicación de la lista de correo ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) confirma que gcc históricamente usa cambios aritméticos para entradas firmadas, por lo que es muy poco probable que FreeBSD gcc 4.2.1 use cambio sin firmar. Actualicé su publicación para corregir eso y el párrafo anterior que decía que ambos usaban shr, cuando en realidad es SAR que ambos usan. El SHR es cómo extrae el bit de signo para el/
caso. También se incluye un enlace godbolt.Solo una nota adicional:
x * = 0.5 a menudo será más rápido en algunos lenguajes basados en VM, especialmente actionscript, ya que la variable no tendrá que verificarse para dividir por 0.
fuente
eval
) hacían que eso ocurriera de nuevo cada vez. De todos modos, sí, es una prueba bastante mala, porque es una optimización muy tonta.Use
x = x / 2;
Ox /= 2;
Porque es posible que un nuevo programador trabaje en él en el futuro. Entonces será más fácil para él descubrir qué está pasando en la línea de código. Es posible que todos no estén al tanto de tales optimizaciones.fuente
Lo digo con el propósito de programar competencias. Generalmente tienen entradas muy grandes donde la división por 2 tiene lugar muchas veces y se sabe que la entrada es positiva o negativa.
x >> 1 será mejor que x / 2. Revisé ideone.com ejecutando un programa donde se llevaron a cabo más de 10 ^ 10 divisiones por 2 operaciones. x / 2 tomó casi 5.5s mientras que x >> 1 tomó casi 2.6s para el mismo programa.
fuente
x/2
ax>>1
. Para los valores con signo, casi todas las implementaciones definenx>>1
tener un significado que es equivalentex/2
pero que se puede calcular más rápido cuandox
es positivo y útilmente diferente dex/2
cuandox
es negativo.Yo diría que hay varias cosas a considerar.
Bitshift debería ser más rápido, ya que realmente no se necesita un cálculo especial para cambiar los bits, sin embargo, como se señaló, existen posibles problemas con los números negativos. Si tiene la seguridad de tener números positivos y está buscando velocidad, le recomendaría bitshift.
El operador de división es muy fácil de leer para los humanos. Entonces, si está buscando legibilidad de código, podría usar esto. Tenga en cuenta que el campo de la optimización del compilador ha recorrido un largo camino, por lo que hacer que el código sea fácil de leer y comprender es una buena práctica.
Si buscas un rendimiento puro, recomendaría crear algunas pruebas que podrían hacer las operaciones millones de veces. Pruebe la ejecución varias veces (su tamaño de muestra) para determinar cuál es estadísticamente mejor con su sistema operativo / hardware / compilador / código.
fuente
>>
que sí coincide y lo que/
no.En lo que respecta a la CPU, las operaciones de desplazamiento de bits son más rápidas que las operaciones de división. Sin embargo, el compilador lo sabe y se optimizará adecuadamente en la medida de lo posible, para que pueda codificar de la manera que tenga más sentido y estar tranquilo sabiendo que su código se ejecuta de manera eficiente. Pero recuerde que una
unsigned int
lata (en algunos casos) puede optimizarse mejor que unaint
por las razones previamente señaladas. Si no necesita aritmética con signo, no incluya el bit de signo.fuente
x = x / 2; es el código adecuado para usar ... pero una operación depende de su propio programa de cómo la salida que desea producir.
fuente
Haga sus intenciones más claras ... por ejemplo, si desea dividir, use x / 2 y deje que el compilador lo optimice para cambiar de operador (o cualquier otra cosa).
Los procesadores de hoy no permitirán que estas optimizaciones tengan ningún impacto en el rendimiento de sus programas.
fuente
La respuesta a esto dependerá del entorno en el que esté trabajando.
x /= 2
enx >>= 1
la presencia de un símbolo de división elevará más las cejas en ese ambiente de usando un cambio para efectuar una división.x >>= 1
con un comentario que explique que su razonamiento probablemente sea mejor solo por claridad de propósito.x /= 2
. Es mejor salvar al siguiente programador que mira su código en la doble toma de 10 segundos en su operación de turno que demostrar innecesariamente que sabía que el cambio era más eficiente sin la optimización del compilador.Todos estos suponen enteros sin signo. El cambio simple probablemente no sea lo que desea para firmar. Además, DanielH plantea un buen punto sobre el uso
x *= 0.5
de ciertos lenguajes como ActionScript.fuente
mod 2, prueba para = 1. no sé la sintaxis en c. Pero esto puede ser más rápido.
fuente
generalmente el desplazamiento a la derecha divide:
esto a veces se usa para acelerar los programas a costa de la claridad. No creo que debas hacerlo. El compilador es lo suficientemente inteligente como para realizar la aceleración automáticamente. Esto significa que poner un turno no te aporta nada a costa de la claridad .
Echa un vistazo a esta página desde la programación práctica de C ++.
fuente
(x+128)>>8
se calcularía para valoresx
no cercanos al máximo, ¿cómo podría hacerlo concisamente sin un cambio? Tenga en cuenta que(x+128)/256
no funcionará. ¿Conoces alguna buena expresión que lo hará?Obviamente, si está escribiendo su código para el próximo tipo que lo lea, busque la claridad de "x / 2".
Sin embargo, si la velocidad es su objetivo, pruébelo en ambos sentidos y cronometra los resultados. Hace unos meses trabajé en una rutina de convolución de mapa de bits que consistía en recorrer una serie de enteros y dividir cada elemento entre 2. Hice todo tipo de cosas para optimizarlo, incluido el viejo truco de sustituir "x >> 1" por "x / 2 ".
Cuando realmente cronometré en ambos sentidos, descubrí para mi sorpresa que x / 2 era más rápido que x >> 1
Esto estaba usando Microsoft VS2008 C ++ con las optimizaciones predeterminadas activadas.
fuente
En términos de rendimiento. Las operaciones de cambio de la CPU son significativamente más rápidas que los códigos de operación divididos. Entonces, dividir entre dos o multiplicar por 2, etc., se beneficia de las operaciones de turno.
En cuanto a la apariencia. Como ingenieros, ¿cuándo nos apegamos tanto a los cosméticos que incluso las mujeres hermosas no usan! :)
fuente
X / Y es el correcto ... y el operador de desplazamiento ">>" ... si queremos que dos dividan un entero, podemos usar el operador de dividendo (/). El operador shift se usa para cambiar los bits.
x = x / 2; x / = 2; podemos usar así ...
fuente
Si bien x >> 1 es más rápido que x / 2, el uso adecuado de >> cuando se trata de valores negativos es un poco más complicado. Requiere algo similar a lo siguiente:
fuente