¿Este algoritmo de intercambio de valores XOR todavía está en uso o es útil?

25

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?

Quinton Bernhardt
fuente
50
Todavía se usa ampliamente para presumir y hacer malas preguntas en la entrevista; Eso es todo.
Michael Borgwardt
44
¿Es esto algo así como el truco: a = a + b, b = ab, a = ab?
Pieter B
44
@PieterB sí, ambos son casos de este stackoverflow.com/questions/1826159/…
jk.
Entonces ... Guarda un registro pero paga con 3 instrucciones más. Creo que la versión temporal sería más rápida de todos modos.
Billy ONeal
1
El intercambio de valores de @MSalters no ha sido un problema en x86 desde el principio debido a la instrucción xchg. OTOH intercambiando 2 ubicaciones de memoria requiere 3 xchgs :)
Netch

Respuestas:

41

Cuando se usa, xorswapexiste 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 xorswapse 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 una XCHGinstrucció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.

zxcdw
fuente
18
Si ambos valores son iguales, entonces aún obtendría el resultado correcto. a: 0101 ^ 0101 = 0000; b: 0101 ^ 0000 = 0101; a: 0101 ^ 0000 = 0101;
Bobson el
1
Lo que dijo @ GlenH7. -1-> +1.
Bobson el
1
Todavía parece tener la frase sobre "Cuando se usa xorswap, existe el peligro de proporcionar la misma variable que ambos argumentos a la función que pone a cero dicha variable debido a que se corrige por sí misma y convierte todos los bits a cero". .. ¿No estaba destinado a haber sido eliminado?
Chris
99
@ Chris No, al principio había escrito que si los valores fueran idénticos como a=10, b=10si 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 hicieras xorswap(a, a), ase pondría a cero, lo que originalmente quise decir, pero estaba siendo estúpido. :)
zxcdw
3
Es bastante relevante en ciertos idiomas: pequeños y pequeños peligros como ese hacen que sea difícil encontrar errores en el futuro cuando se trata de dos punteros que de alguna manera estaban configurados para apuntar a la misma dirección más de 100 líneas que no ves.
Drake Clarris el
14

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
2
Los registros no son realmente tan abundantes en muchos contextos. Incluso si un procesador tiene un amplio suministro de registros, cada registro utilizado en un ISR es un registro que debe guardarse de antemano y restaurarse después. Si un ISR toma veinte ciclos y se supone que se ejecuta cada cuarenta ciclos, cada ciclo adicional agregado al ISR degradará el rendimiento del sistema en cinco puntos porcentuales.
supercat
8

¿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.

jimwise
fuente
2

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).

Netch
fuente