Als ich anfing zu arbeiten, zeigte mir ein Mainframe Assembler-Programmierer, wie sie zu Werten wechseln, ohne den traditionellen Algorithmus von:
a = 0xBABE
b = 0xFADE
temp = a
a = b
b = temp
Was sie verwendeten, um zwei Werte zu tauschen - von einem Bit zu einem großen Puffer - war:
a = 0xBABE
b = 0xFADE
a = a XOR b
b = b XOR a
a = a XOR b
jetzt
b == 0xBABE
a == 0xFADE
Dadurch wurde der Inhalt von 2 Objekten ausgetauscht, ohne dass ein dritter temporärer Speicherplatz erforderlich war.
Meine Frage ist: Ist dieser XOR-Swap-Algorithmus noch in Gebrauch und wo ist er noch anwendbar?
algorithms
Quinton Bernhardt
quelle
quelle
Antworten:
Bei Verwendung besteht
xorswap
die Gefahr, dass dieselbe Variable wie beide Argumente an die Funktion übergeben wird, die die Variablexor
auf Null setzt , da sie mit sich selbst verbunden ist und alle Bits auf Null setzt. Dies selbst würde natürlich zu unerwünschtem Verhalten führen, unabhängig vom verwendeten Algorithmus, aber das Verhalten könnte überraschend und auf den ersten Blick nicht offensichtlich sein.Traditionell
xorswap
wurde es für Implementierungen auf niedriger Ebene zum Austauschen von Daten zwischen Registern verwendet. In der Praxis gibt es bessere Alternativen für den Austausch von Variablen in Registern. Zum Beispiel hat Intels x86 einenXCHG
Befehl, der den Inhalt zweier Register vertauscht. Oft wird ein Compiler die Semantik einer solchen Funktion herausfinden (er tauscht den Inhalt der übergebenen Werte aus) und kann bei Bedarf seine eigenen Optimierungen vornehmen. Wenn Sie also versuchen, etwas so Triviales wie eine Swap-Funktion zu optimieren, erhalten Sie eigentlich nichts in der Praxis. Es ist am besten, die offensichtliche Methode zu verwenden, es sei denn, es gibt einen nachgewiesenen Grund, warum es unterlegen wäre, in der Problemdomäne xorswap zu sagen.quelle
a: 0101 ^ 0101 = 0000; b: 0101 ^ 0000 = 0101; a: 0101 ^ 0000 = 0101;
-1
->+1
.a=10, b=10
, und wenn Sie dies tatenxorswap(a,b)
, dies funktionieren würde und nicht die Variablen auf Null setzen würde, was falsch ist und jetzt entfernt wird. Aber wenn Sie habenxorswap(a, a)
dann aufa
Null gestellt bekommen würden , die ich hatte ursprünglich gemeint war aber dumm. :)Der Schlüssel zur Antwort liegt in der Frage "Arbeiten mit einem Mainframe-Assembler-Programmierer" in den Tagen vor dem Compiler. Wenn Menschen sich mit Montageanleitungen und handgefertigten exakten Lösungen für ein bestimmtes Hardwareteil hinknien (das möglicherweise auf einem anderen Modell desselben Computers funktioniert oder nicht), wirkten sich Probleme wie das Timing der Festplatten und des Trommelspeichers auf die Codequalität aus geschrieben - lies Die Geschichte von Mel, wenn man das Bedürfnis hat, nostalgisch zu sein.
In früheren Zeiten waren Register und Speicher knapp, und jeder Trick, nicht um ein weiteres Byte oder ein weiteres Wort des leitenden Architekten bitten zu müssen, sparte Zeit - sowohl beim Schreiben des Codes als auch bei der Ausführung.
Diese Tage sind vorbei. Der Trick, zwei Dinge zu tauschen, ohne einen dritten zu benutzen, ist ein Trick. Sowohl Speicher als auch Register sind in modernen Computern reichlich vorhanden, und Menschen schreiben keine Assemblierung mehr. Wir haben unseren Compilern alle unsere Tricks beigebracht, und sie machen es besser als wir. Wahrscheinlich hat der Compiler etwas noch besser gemacht, als wir es getan hätten. In den meisten Fällen müssen wir Assembler manchmal aus irgendeinem Grund in ein inneres Bit einer engen Schleife schreiben ... aber nicht, um ein Register oder ein Wort aus dem Speicher zu speichern.
Es könnte wieder nützlich sein, wenn man in einem besonders eingeschränkten Mikrocontroller arbeitet, aber die Optimierung eines Swap ist dann wahrscheinlich nicht die Ursache für sein Problem - zu schlau zu sein ist wahrscheinlich ein Problem.
quelle
Wird es funktionieren? Ja.
Solltest du es benutzen? Nein.
Diese Art der Mikrooptimierung ist sinnvoll, wenn:
Sie haben sich den Code angesehen, den der Compiler auf einfache Weise generiert (Zuweisung und temporärer Code), und haben festgestellt, dass der XOR-Ansatz schnelleren Code generiert
Sie haben Ihre App profiliert und festgestellt, dass die Kosten für den unkomplizierten Ansatz die Klarheit des Codes (und die daraus resultierenden Einsparungen bei der Wartbarkeit) überwiegen.
Bis zum ersten Punkt sollten Sie dem Compiler vertrauen, es sei denn, Sie haben diese Messung durchgeführt. Wenn die Semantik klar ist, was Sie tun möchten, kann der Compiler eine Menge Tricks anwenden, einschließlich der Neuanordnung des variablen Zugriffs, sodass der Swap überhaupt nicht benötigt wird, oder der Eingabe der Anweisungen auf Maschinenebene, die am schnellsten sind Swap für einen bestimmten Datentyp. "Tricks" wie der XOR-Tausch erschweren es dem Compiler zu sehen, was Sie versuchen, und machen es daher weniger in der Lage, solche Optimierungen anzuwenden.
Was gewinnen Sie bis zum zweiten Punkt für die zusätzliche Komplexität? Auch wenn Sie den XOR-Ansatz schneller gemessen und gefunden haben, hat dies genügend Auswirkungen, um einen weniger klaren Ansatz zu rechtfertigen? Woher weißt du das?
Schließlich sollten Sie prüfen, ob es eine Standard-Swap-Funktion für Ihre Plattform / Sprache gibt - die C ++ STL bietet beispielsweise eine Template-Swap-Funktion, die in der Regel für Ihren Compiler / Ihre Plattform optimiert ist.
quelle
Mein Kollege berichtete, dass dies ein grundlegender Trick ist, den er während seines Studiums an einer Universität für einen Programmierer automatisierter Systeme gelehrt hat. Viele dieser Systeme sind eingebettete Systeme mit begrenzten Ressourcen, und es könnte ihnen ein freies Register fehlen, um den temporären Wert beizubehalten. In diesem Fall wird solch ein kniffliger Austausch (oder dessen Analogie mit Addieren und Subtrahieren) immer wichtiger, so dass er immer noch verwendet wird.
Es ist jedoch zu beachten, dass diese beiden Stellen nicht identisch sein können, da im letzteren Fall beide Werte effektiv auf Null gesetzt werden. Normalerweise beschränkt sich dies auf offensichtliche Fälle wie das Austauschen eines Registers und eines Speicherorts.
Mit x86 erfüllen die Befehle xchg und cmpxchg in den meisten Fällen die Anforderungen, aber RISCs werden im Allgemeinen nicht damit abgedeckt (außer Sparc).
quelle