Cuando comencé a trabajar, un programador de ensamblador de mainframe me mostró cómo intercambian valores sin usar el algoritmo tradicional de:
a = 0xBABE
b = 0xFADE
temp = a
a = b
b = temp
Lo que usaron para intercambiar dos valores, desde un búfer a un búfer grande, fue:
a = 0xBABE
b = 0xFADE
a = a XOR b
b = b XOR a
a = a XOR b
ahora
b == 0xBABE
a == 0xFADE
que intercambió el contenido de 2 objetos sin la necesidad de un tercer espacio de almacenamiento temporal.
Mi pregunta es: ¿Este algoritmo de intercambio XOR todavía está en uso y dónde sigue siendo aplicable?
algorithms
Quinton Bernhardt
fuente
fuente
Respuestas:
Cuando se usa,
xorswap
existe el peligro de suministrar la misma variable que ambos argumentos a la función que pone a cero dicha variable debido a que estáxor
'd consigo misma', lo que convierte todos los bits en cero. Por supuesto, esto en sí mismo daría como resultado un comportamiento no deseado independientemente del algoritmo utilizado, pero el comportamiento puede ser sorprendente y no obvio a primera vista.Tradicionalmente
xorswap
se ha utilizado para implementaciones de bajo nivel para intercambiar datos entre registros. En la práctica, hay mejores alternativas para intercambiar variables en los registros. Por ejemplo, el x86 de Intel tiene unaXCHG
instrucción que intercambia el contenido de dos registros. Muchas veces un compilador descubrirá la semántica de una función de este tipo (intercambia el contenido de los valores que se le pasan) y puede hacer sus propias optimizaciones si es necesario, por lo que tratar de optimizar algo tan trivial como una función de intercambio realmente no le compra nada en la práctica. Es mejor usar el método obvio a menos que haya una razón comprobada por la cual sería inferior decir xorswap dentro del dominio del problema.fuente
a: 0101 ^ 0101 = 0000; b: 0101 ^ 0000 = 0101; a: 0101 ^ 0000 = 0101;
-1
->+1
.a=10, b=10
si lo hicieran,xorswap(a,b)
eso funcionaría y no reduciría a cero las variables que son falsas y ahora se eliminan. Pero si lo hicierasxorswap(a, a)
,a
se pondría a cero, lo que originalmente quise decir, pero estaba siendo estúpido. :)La clave de la respuesta está en la pregunta: "trabajar en un programador de ensamblador de mainframe", en los días previos al compilador. Cuando los humanos se agacharon con las instrucciones de ensamblaje y las soluciones exactas hechas a mano para una pieza de hardware en particular (que puede o no funcionar en otro modelo de la misma computadora), problemas como el tiempo de los discos duros y la memoria del tambor tuvieron un impacto en cómo era el código escrito: lea La historia de Mel si se siente la necesidad de sentir nostalgia).
En estos días pasados, los registros y la memoria eran escasos y cualquier truco para no tener que rogar por otro byte o palabra de memoria del arquitecto principal se ahorró tiempo, tanto al escribir el código como al tiempo de ejecución.
Aquellos días se han ido. El truco de intercambiar dos cosas sin usar un tercero es un truco. La memoria y los registros son abundantes en la informática moderna y los humanos ya no escriben ensamblajes. Hemos enseñado todos nuestros trucos a nuestros compiladores, y hacen un mejor trabajo que nosotros. Lo más probable es que el compilador hizo algo aún mejor que lo que hubiéramos hecho. En la mayoría de los casos, a veces necesitamos escribir el ensamblaje en algún bit interno de un circuito cerrado por alguna razón ... pero no es para guardar un registro o una palabra de memoria.
Puede ser útil nuevamente si uno está trabajando en un microcontrolador particularmente limitado, pero optimizar un intercambio no es la fuente del problema en ese momento; tratar de ser demasiado inteligente es más probable que sea un problema.
fuente
¿Funcionará? Sí.
¿Deberías usarlo? No.
Este tipo de micro-optimización tendría sentido si:
ha mirado el código que genera el compilador para la forma directa de hacerlo (asignación y temporal) y ha decidido que el enfoque XOR genera un código más rápido
ha perfilado su aplicación y ha encontrado que el costo del enfoque directo supera la claridad del código (y los ahorros resultantes en mantenimiento)
Hasta el primer punto, a menos que haya hecho esta medición, debería confiar en el compilador. Cuando la semántica de lo que está tratando de hacer es clara, el compilador puede hacer muchos trucos, incluyendo reorganizar el acceso variable para que no sea necesario el intercambio, o alinear las instrucciones de nivel de máquina que proporcionen más rápido intercambiar por un tipo de datos dado. Los "trucos" como el intercambio XOR hacen que sea más difícil para el compilador ver lo que está tratando de hacer y, por lo tanto, es menos capaz de aplicar tales optimizaciones.
Para el segundo punto, ¿qué está ganando por la complejidad añadida? Incluso si ha medido y encontrado el enfoque XOR más rápido, ¿está teniendo suficiente impacto para justificar un enfoque menos claro? ¿Cómo lo sabes?
Finalmente, debe analizar si existe una función de intercambio estándar para su plataforma / lenguaje: el STL de C ++, por ejemplo, proporciona una función de intercambio de plantillas que tenderá a estar altamente optimizada para su compilador / plataforma.
fuente
Mi colega informó que este es un truco básico que ha enseñado en la universidad estudiando para un programador de sistemas automatizados. Muchos de estos sistemas son integrados con recursos limitados y podrían carecer de un registro gratuito para mantener el valor temporal; en ese caso, ese intercambio complicado (o su análogo con sumar y restar) se está volviendo vital, por lo que todavía se usa.
Pero se debe tener cuidado al usar que estas dos ubicaciones no pueden ser idénticas porque en el último caso efectivamente pondrá a cero ambos valores. Por lo tanto, generalmente se limita a casos obvios como el intercambio de un registro y una ubicación de memoria.
Con x86, las instrucciones xchg y cmpxchg satisfacen la necesidad en la mayoría de los casos, pero los RISC generalmente no están cubiertos con ellas (excepto Sparc).
fuente