Questo algoritmo di scambio di valori XOR è ancora in uso o utile?


25

Quando ho iniziato a lavorare un programmatore di assemblatori di mainframe mi ha mostrato come scambiano i valori senza usare l'algoritmo tradizionale di:

a = 0xBABE
b = 0xFADE

temp = a
a = b
b = temp

Quello che usavano per scambiare due valori - da un bit a un buffer di grandi dimensioni - era:

a = 0xBABE
b = 0xFADE

a = a XOR b
b = b XOR a
a = a XOR b

adesso

b == 0xBABE
a == 0xFADE

che ha scambiato il contenuto di 2 oggetti senza la necessità di un terzo spazio di conservazione della temperatura.

La mia domanda è: l'algoritmo di scambio XOR è ancora in uso e dove è ancora applicabile?


50
È ancora ampiamente utilizzato per mettere in mostra e cattive domande sul colloquio; questo è tutto.
Michael Borgwardt,

4
È qualcosa di simile al trucco: a = a + b, b = ab, a = ab?
Pieter B,

4
@PieterB sì, sono entrambi i casi di questo stackoverflow.com/questions/1826159/…
jk.

Quindi ... Salvi un registro ma paghi con altre 3 istruzioni. Penso che la versione temporanea sarebbe comunque più veloce.
Billy ONeal,

1
Lo scambio di valori @MSalters non è stato un problema in x86 sin dall'inizio a causa dell'istruzione xchg. Lo scambio OTOH di 2 posizioni di memoria richiede 3 xchg :)
Netch

Risposte:


41

Quando si usa xorswapc'è il pericolo di fornire la stessa variabile di entrambi gli argomenti alla funzione che azzera la suddetta variabile a causa del fatto che si trova xorcon se stesso e trasforma tutti i bit a zero. Naturalmente questo stesso comporterebbe un comportamento indesiderato indipendentemente dall'algoritmo utilizzato, ma il comportamento potrebbe essere sorprendente e non ovvio a prima vista.

Tradizionalmente xorswapè stato utilizzato per implementazioni di basso livello per lo scambio di dati tra registri. In pratica ci sono alternative migliori per scambiare variabili nei registri. Ad esempio, Intel x86 ha XCHGun'istruzione che scambia il contenuto di due registri. Molte volte un compilatore capirà la semantica di una tale funzione (scambia il contenuto dei valori passati ad essa) e può apportare le proprie ottimizzazioni se necessario, quindi cercare di ottimizzare qualcosa di banale come una funzione di scambio non ti compra davvero nulla in pratica. È meglio usare il metodo ovvio a meno che non ci sia una ragione provata per cui sarebbe inferiore dire xorswap all'interno del dominio problematico.


18
Se entrambi i valori sono uguali, si otterrebbe comunque il risultato corretto. a: 0101 ^ 0101 = 0000; b: 0101 ^ 0000 = 0101; a: 0101 ^ 0000 = 0101;
Bobson

1
Cosa ha detto @ GlenH7. -1-> +1.
Bobson

1
Sembra che tu abbia ancora la linea "Quando si usa xorswap c'è il pericolo di fornire la stessa variabile di entrambi gli argomenti alla funzione che azzera la suddetta variabile a causa del fatto che è xor'd con se stesso che trasforma tutti i bit a zero.". .. Non è stato pensato per essere rimosso?
Chris,

9
@Chris No, all'inizio avevo scritto che se i valori fossero identici come se a=10, b=10, e se lo facessi xorswap(a,b)funzionerebbe e non azzererebbe le variabili che sono false e ora rimosse. Ma se lo facessi, xorswap(a, a)allora asaresti azzerato, cosa che in origine intendevo ma che era stupido. :)
zxcdw,

3
È abbastanza rilevante in alcune lingue - piccoli piccoli pericoli come quello rendono difficile trovare bug in futuro quando si ha a che fare con due puntatori che in qualche modo sono stati impostati in modo da puntare allo stesso indirizzo di oltre 100 righe che non si vedono.
Drake Clarris,

14

La chiave per la risposta è nella domanda - "lavorare con un programmatore assembler mainframe" - nei giorni precedenti il ​​compilatore. Quando gli esseri umani si sono accalcati con le istruzioni di assemblaggio e le soluzioni esatte realizzate a mano per un particolare componente hardware (che potrebbe o non potrebbe funzionare su un altro modello dello stesso computer), problemi come la tempistica dei dischi rigidi e la memoria del tamburo hanno avuto un impatto su come il codice era scritto - leggi La storia di Mel se uno sente il bisogno di essere nostalgico).

In questi tempi passati, i registri e la memoria erano entrambi scarsi e ogni trucco per non dover chiedere un altro byte o parola di memoria dall'architetto capo è stato risparmiato tempo, sia nella scrittura del codice che nel tempo di esecuzione.

Quei giorni sono andati. Il trucco di scambiare due cose senza usare un terzo è un trucco. La memoria e i registri sono entrambi abbondanti nell'informatica moderna e gli umani non scrivono più assemblaggi. Abbiamo insegnato tutti i nostri trucchi ai nostri compilatori e loro fanno un lavoro migliore di noi. È probabile che il compilatore abbia fatto qualcosa di persino migliore di quello che avremmo fatto. Nella maggior parte dei casi, a volte abbiamo bisogno di scrivere assembly in qualche bit interno di un ciclo stretto per qualche motivo ... ma non è per salvare un registro o una parola di memoria.

Potrebbe essere di nuovo utile se si lavora in un microcontrollore particolarmente limitato, ma l'ottimizzazione di uno swap non è probabilmente la causa del proprio problema: cercare di essere troppo intelligenti è più probabilmente un problema.


2
I registri non sono davvero così numerosi in molti contesti. Anche se un processore dispone di una vasta gamma di registri, ogni registro utilizzato in un ISR è un registro che deve essere precedentemente salvato e ripristinato in seguito. Se un ISR impiega venti cicli e si suppone che esegua ogni quaranta cicli, ogni ciclo aggiuntivo aggiunto all'ISR peggiorerà le prestazioni del sistema di cinque punti percentuali.
supercat,

8

Funzionerà? Sì.

Dovresti usarlo? No.

Questo tipo di micro-ottimizzazione avrebbe senso se:

  • hai esaminato il codice generato dal compilatore per il modo semplice di eseguire ciò (assegnazione e un temporaneo) e hai deciso che l'approccio XOR genera codice più veloce

  • hai profilato la tua app e hai riscontrato che il costo dell'approccio diretto supera la chiarezza del codice (e il conseguente risparmio in termini di manutenibilità)

Al primo punto, a meno che tu non abbia fatto questa misurazione, dovresti fidarti del compilatore. Quando la semantica di ciò che stai cercando di fare è chiara, ci sono molti trucchi che il compilatore può fare, incluso riorganizzare l'accesso variabile in modo che lo swap non sia affatto necessario o incorporare qualsiasi istruzione a livello di macchina fornisca il più veloce scambiare per un determinato tipo di dati. "Trucchi" come lo scambio XOR rendono più difficile per il compilatore vedere cosa si sta tentando di fare e quindi rendere meno in grado di applicare tali ottimizzazioni.

Al secondo punto, cosa stai guadagnando per la complessità aggiunta? Anche se hai misurato e trovato più rapidamente l'approccio XOR, questo ha un impatto sufficiente a giustificare un approccio meno chiaro? Come lo sai?

Infine, dovresti verificare se esiste una funzione di scambio standard per la tua piattaforma / linguaggio - il C ++ STL, ad esempio, fornisce una funzione di scambio di modelli che tenderà ad essere altamente ottimizzata per il tuo compilatore / piattaforma.


2

Il mio collega ha riferito che questo è un trucco di base che ha insegnato all'università studiando per un programmatore di sistemi automatizzati. Molti di questi sistemi sono integrati con risorse limitate e potrebbero non avere un registro gratuito per conservare il valore temporaneo; in tal caso, tale scambio complicato (o il suo analogo con l'aggiunta e la sottrazione) sta diventando vitale, quindi viene ancora utilizzato.

Ma ci si preoccuperà di usarlo che queste due posizioni non possono essere identiche perché in quest'ultimo caso azzererà entrambi i valori. Quindi di solito si limita a casi ovvi come lo scambio di un registro e una posizione di memoria.

Con x86, le istruzioni xchg e cmpxchg soddisfano la necessità nella maggior parte dei casi, ma i RISC generalmente non ne sono coperti (tranne Sparc).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.