Cet algorithme d'échange de valeurs XOR est-il toujours utilisé ou utile

25

Lorsque j'ai commencé à travailler, un programmeur assembleur mainframe m'a montré comment ils pouvaient passer aux valeurs sans utiliser l'algorithme traditionnel de:

a = 0xBABE
b = 0xFADE

temp = a
a = b
b = temp

Ce qu'ils ont utilisé pour échanger deux valeurs - d'un peu à un grand tampon - était:

a = 0xBABE
b = 0xFADE

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

à présent

b == 0xBABE
a == 0xFADE

qui a échangé le contenu de 2 objets sans avoir besoin d'un troisième espace de maintien temporaire.

Ma question est la suivante: cet algorithme d'échange XOR est-il toujours utilisé et où est-il toujours applicable?

Quinton Bernhardt
la source
50
Il est encore largement utilisé pour montrer et de mauvaises questions d'entrevue; C'est à peu près ça.
Michael Borgwardt
4
Est-ce quelque chose comme l'astuce: a = a + b, b = ab, a = ab?
Pieter B
4
@PieterB oui ce sont les deux cas de ce stackoverflow.com/questions/1826159/…
jk.
Alors ... Vous enregistrez un registre mais payez avec 3 instructions supplémentaires. Je pense que la version temporaire serait plus rapide de toute façon.
Billy ONeal
1
L'échange de valeurs @MSalters n'a pas été un problème en x86 depuis le début en raison de l'instruction xchg. L'échange OTOH de 2 emplacements de mémoire nécessite 3 xchgs :)
Netch

Réponses:

41

Lors de l'utilisation, xorswapil y a un risque de fournir la même variable que les deux arguments à la fonction qui met à zéro ladite variable car elle est xoravec elle-même, ce qui met tous les bits à zéro. Bien sûr, cela entraînerait un comportement indésirable quel que soit l'algorithme utilisé, mais le comportement pourrait être surprenant et pas évident à première vue.

Traditionnellement, xorswapa été utilisé pour les implémentations de bas niveau pour l'échange de données entre les registres. En pratique, il existe de meilleures alternatives pour l'échange de variables dans les registres. Par exemple, le x86 d'Intel a une XCHGinstruction qui permute le contenu de deux registres. Plusieurs fois, un compilateur trouvera la sémantique d'une telle fonction (il échange le contenu des valeurs qui lui sont transmises) et peut faire ses propres optimisations si nécessaire, donc essayer d'optimiser quelque chose d'aussi trivial qu'une fonction d'échange ne vous achète vraiment rien en pratique. Il est préférable d'utiliser la méthode évidente à moins qu'il n'y ait une raison avérée pour laquelle il serait inférieur à dire xorswap dans le domaine problématique.

zxcdw
la source
18
Si les deux valeurs sont identiques, vous obtiendrez toujours le résultat correct. a: 0101 ^ 0101 = 0000; b: 0101 ^ 0000 = 0101; a: 0101 ^ 0000 = 0101;
Bobson
1
Qu'est-ce que @ GlenH7 a dit. -1-> +1.
Bobson
1
Vous semblez toujours avoir la ligne sur "Lors de l'utilisation de xorswap, il y a un risque de fournir la même variable que les deux arguments à la fonction qui met à zéro ladite variable car elle est xor'd avec elle-même, ce qui met tous les bits à zéro.". .. N'était-ce pas censé avoir été supprimé?
Chris
9
@Chris Non, au début, j'avais écrit que si les valeurs étaient identiques comme si a=10, b=10, et si vous le faisiez, xorswap(a,b)cela fonctionnerait et ne mettrait pas à zéro les variables, ce qui est faux et maintenant supprimé. Mais si vous le faisiez xorswap(a, a)alors a, vous vous mettriez à zéro, ce que je voulais dire à l'origine, mais j'étais stupide. :)
zxcdw
3
C'est assez pertinent dans certaines langues - de petits petits dangers comme celui-ci rendent difficile la recherche de bogues à l'avenir lorsque vous traitez avec deux pointeurs qui étaient en quelque sorte définis pour pointer vers la même adresse, plus de 100 lignes que vous ne voyez pas.
Drake Clarris
14

La clé de la réponse se trouve dans la question - "travailler avec un programmeur assembleur mainframe" - dans les jours précédant le compilateur. Lorsque les humains se sont penchés sur les instructions de montage et ont élaboré à la main des solutions exactes pour un matériel particulier (qui peut ou non fonctionner sur un autre modèle du même ordinateur), des problèmes tels que la synchronisation des disques durs et de la mémoire du tambour ont eu un impact sur la façon dont le code était écrit - lire L'histoire de Mel si l'on ressent le besoin d'être nostalgique).

À cette époque révolue, les registres et la mémoire étaient à la fois rares et toute astuce pour ne pas avoir à mendier un autre octet ou mot de mémoire de l'architecte principal a été économisée du temps - à la fois dans l'écriture du code et le temps d'exécution.

Ces jours sont révolus. L'astuce consistant à échanger deux choses sans en utiliser une troisième est une astuce. La mémoire et les registres sont tous deux abondants dans l'informatique moderne et les humains n'écrivent plus d'assemblage. Nous avons enseigné toutes nos astuces à nos compilateurs, et ils font mieux que nous. Il y a de fortes chances que le compilateur ait fait quelque chose de mieux que ce que nous aurions fait. Dans la plupart des cas, nous avons parfois besoin d'écrire l'assembly dans un morceau interne d'une boucle serrée pour une raison quelconque ... mais ce n'est pas pour enregistrer un registre ou un mot de mémoire.

Cela pourrait être utile à nouveau si l'on travaille dans un microcontrôleur particulièrement limité, mais l'optimisation d'un échange n'est alors probablement pas la source de son problème - essayer d'être trop intelligent est probablement un problème.


la source
2
Les registres ne sont pas vraiment si abondants dans de nombreux contextes. Même si un processeur dispose d'une ample offre de registres, chaque registre utilisé dans un ISR est un registre qui doit être sauvegardé au préalable et restauré par la suite. Si un ISR prend vingt cycles et est censé s'exécuter tous les quarante cycles, chaque cycle supplémentaire ajouté à l'ISR dégradera les performances du système de cinq points de pourcentage.
supercat
8

Est-ce que ça marchera? Oui.

Devriez-vous l'utiliser? Non.

Ce type de micro-optimisation aurait du sens si:

  • vous avez regardé le code que le compilateur génère pour la manière simple de le faire (affectation et temporaire) et décidé que l'approche XOR génère un code plus rapide

  • vous avez profilé votre application et constaté que le coût de l'approche simple l'emporte sur la clarté du code (et les économies de maintenance qui en résultent)

Au premier point, à moins que vous n'ayez fait cette mesure, vous devriez faire confiance au compilateur. Lorsque la sémantique de ce que vous essayez de faire est claire, le compilateur peut effectuer de nombreuses astuces, y compris la réorganisation de l'accès variable afin que l'échange ne soit pas nécessaire du tout, ou en alignant les instructions au niveau de la machine les plus rapides permuter pour un type de données donné. Les "astuces" telles que le swap XOR rendent plus difficile pour le compilateur de voir ce que vous essayez de faire, et le rendent ainsi moins en mesure d'appliquer de telles optimisations.

Au deuxième point, que gagnez-vous pour la complexité supplémentaire? Même si vous avez mesuré et trouvé l'approche XOR plus rapidement, cela a-t-il suffisamment d'impact pour justifier une approche moins claire? Comment le sais-tu?

Enfin, vous devriez vérifier s'il existe une fonction d'échange standard pour votre plate-forme / langage - le C ++ STL, par exemple, fournit une fonction d'échange de modèle qui aura tendance à être hautement optimisée pour votre compilateur / plate-forme.

Jimwise
la source
2

Mon collègue a indiqué que c'est un truc de base qu'il a enseigné à l'université en étudiant pour un programmeur de systèmes automatisés. Beaucoup de ces systèmes sont des systèmes intégrés avec des ressources limitées et ils pourraient ne pas avoir de registre gratuit pour conserver la valeur temporaire; dans ce cas, un tel échange délicat (ou son analogue avec l'addition et la soustraction) devient vital et reste donc utilisé.

Mais il faut faire attention en l'utilisant que ces deux emplacements ne peuvent pas être identiques car dans ce dernier cas il mettra effectivement à zéro les deux valeurs. Donc, généralement, cela se limite à des cas évidents comme l'échange d'un registre et d'un emplacement de mémoire.

Avec x86, les instructions xchg et cmpxchg satisfont le besoin dans la plupart des cas, mais les RISC ne sont généralement pas couverts par celles-ci (sauf Sparc).

Netch
la source