Ugrás a tartalomhoz

XOR csere algoritmus

Ellenőrzött
A Wikipédiából, a szabad enciklopédiából
With three XOR operations the binary values 1010 and 0011 are exchanged between variables.
Az XOR swap algoritmus használata apró adatrészletek cseréjére változók között ideiglenes tárolás használata nélkül

A számítógép-programozásban a kizáró VAGY csere ('XOR swap) egy olyan algoritmus, amely a kizáró vagy (XOR(wd)) bitműveletet használja két változó értékének felcserélésére anélkül, hogy az általában szükséges ideiglenes változót használná.

Az algoritmus elsősorban újdonság, és a kizáró vagy (exclusive OR, vagy XOR) művelet tulajdonságainak bemutatására szolgál. Néha programoptimalizálásként is emlegetik, de szinte nincs olyan eset, amikor a kizáró vagy művelettel végrehajtott csere előnyt biztosítana a standard, nyilvánvaló technikával szemben.

A hagyományos csere egy ideiglenes tárolási változó használatát igényli. Az XOR cserealgoritmus használatával azonban nincs szükség ideiglenes tárolásra. Az algoritmus a következő:[1][2]

X := Y XOR X; // XOR műveletet alkalmaz az értékekre, és az eredményt X-ben tárolja
Y := X XOR Y; // XOR műveletet alkalmaz az értékekre, és az eredményt Y-ban tárolja
X := Y XOR X; // XOR műveletet alkalmaz az értékekre, és az eredményt X-ben tárolja

Mivel az XOR egy kommutatív művelet, az X XOR Y vagy az Y XOR X felcserélhetően használható az előző három sorban. Megjegyzendő, hogy egyes architektúrákon az XOR utasítás első operandusa meghatározza a művelet eredményének tárolási célhelyét, megakadályozva ezt a felcserélhetőséget. Az algoritmus jellemzően három gépi kódú utasításnak felel meg, amelyeket a következő táblázat három sorában található megfelelő pszeudokód és assembly utasítások képviselnek:

PszeudokódIBM System/370 assemblyx86 assemblyRISC-V assembly
X := X XOR YXR R1,R2xor eax, ebxxor x10, x11
Y := Y XOR XXR R2,R1xor ebx, eaxxor x11, x10
X := X XOR YXR R1,R2xor eax, ebxxor x10, x11

A fenti System/370 assembly kódmintában az R1 és R2 különálló regiszterek, és minden XR művelet az első argumentumban megnevezett regiszterben hagyja az eredményét. x86 assembly használatával az X és Y értékek az eax és ebx regiszterekben találhatók (rendre), az xor pedig a művelet eredményét az első regiszterbe helyezi. A RISC-V assemblyben az X és Y értékek az X10 és X11 regiszterekben találhatók, az xor pedig a művelet eredményét az első regiszterbe helyezi (ugyanúgy, mint az x86).

A pszeudokódban vagy a magas szintű nyelvi verzióban vagy implementációban azonban az algoritmus kudarcot vall, ha x és y ugyanazt a tárolási helyet használja, mivel az adott helyen tárolt értéket az első XOR utasítás lenullázza, majd nulla marad; nem „cserélődik önmagával”. Ez nem ugyanaz, mintha x és y értékei megegyeznének. A probléma csak akkor jelentkezik, ha x és y ugyanazt a tárolási helyet használja, ebben az esetben az értéküknek már eleve egyenlőnek kell lennie. Vagyis, ha x és y ugyanazt a tárolási helyet használja, akkor a sor:

X := X XOR Y

nullára állítja x-et (mivel x = y, tehát X XOR Y nulla), és y-t is nullára állítja (mivel ugyanazt a tárhelyet használja), aminek következtében x és y elveszítik eredeti értéküket.

A helyesség bizonyítása

[szerkesztés | forrásszöveg szerkesztése]

Az hosszúságú bittömbök feletti XOR binér művelet(wd) a következő tulajdonságokkal rendelkezik (ahol az XOR műveletet jelöli):[a]

  • L1. Kommutativitás:
  • L2. Asszociativitás:
  • L3. Neutrális elem: Létezik egy 0 értékű (N hosszú) bitlánc, amelyre bármely esetén
  • L4. Minden elem a saját inverze(wd): minden esetén .[3]

Tegyük fel, hogy két különböző regiszterünk van, R1 és R2, ahogy az az alábbi táblázatban látható, A és B kezdeti értékekkel. Az alábbi műveleteket sorban végrehajtjuk, és az eredményeket a fent felsorolt tulajdonságok felhasználásával redukáljuk.

Lépés Művelet 1. regiszter 2. regiszter Redukció
0Kezdeti érték—
1R1 := R1 XOR R2—
2R2 := R1 XOR R2

L2
L4
L3
3R1 := R1 XOR R2


L1
L2
L4
L3

Lineáris algebrai értelmezés

[szerkesztés | forrásszöveg szerkesztése]

Mivel az XOR értelmezhető bináris összeadásként, és egy bitpár értelmezhető vektorként egy kétdimenziós vektortérben a kételemű mező(wd) felett, az algoritmus lépései 2×2 mátrixokkal való szorzásként értelmezhetők a kételemű test(wd) felett. Az egyszerűség kedvéért kezdetben tegyük fel, hogy x és y egyaránt egyedi bitek, nem bitvektorok.

Például a lépés:

X := X XOR Y

amely implicit módon tartalmazza a következőt:

Y := Y

megfelel a mátrixnak, mint

.

A műveletek sorrendjét ezután a következőképpen fejezzük ki:

(bináris értékekkel dolgozik, tehát ), amely két sor (vagy oszlop) felcserélésének elemi mátrixát(wd) fejezi ki[4] az egyik elem másikhoz való hozzáadásának transzvekcióival(wd) (nyírásaival[5]).

Az X és Y nem egyetlen bit, hanem n hosszúságú bitvektorokra való általánosításhoz ezeket a 2×2 mátrixokat 2n×2n blokkmátrixokkal(wd) helyettesítjük, például [6][7]

Ezek a mátrixok értékekkel, nem pedig változókkal (tárolási helyekkel) működnek, ezért ez az értelmezés elvonatkoztat a tárolási hely kérdéseitől és attól a problémától, hogy mindkét változó ugyanazon a tárolási helyen van.

Egy C függvény, amely megvalósítja az XOR csere algoritmust:

void XorSwap(int *x, int *y) 
{
  if (x == y) return;
  *x ^= *y;
  *y ^= *x;
  *x ^= *y;
}

A kód először ellenőrzi, hogy a címek különbözőek-e, és egy védelmi záradék[8] segítségével korán kilép a függvényből, ha egyenlőek. Ezen ellenőrzés nélkül, ha egyenlőek lennének, az algoritmus egy *x ^= *x hármast adna, ami nullát eredményezne.[9]

Az XOR cserealgoritmus makróval is definiálható:

#define XORSWAP_UNSAFE(a, b)                                                   \
  ((a) ^= (b), (b) ^= (a),                                                     \
   (a) ^= (b)) /* Nem működik, ha a és b ugyanaz az objektum \
                  - ebben az esetben nullát (0) rendel az objektumhoz */
#define XORSWAP(a, b)                                                         \
  ((&(a) == &(b)) ? (a) /* Különböző értékek ellenőrzése */                    \
                  : XORSWAP_UNSAFE(a, b))

A gyakorlatban elkerülendő okok

[szerkesztés | forrásszöveg szerkesztése]

A modern CPU-architektúrákon az XOR technika lassabb lehet, mint egy ideiglenes változó használata a cseréhez. Legalábbis a legújabb x86-os CPU-kon, mind az AMD, mind az Intel esetében, a regiszterek közötti mozgás rendszeresen nulla késleltetéssel jár. (Ezt MOV-eliminációnak nevezik.) Még ha nincs is elérhető architekturális regiszter, az XCHG utasítás legalább olyan gyors lesz, mint a három XOR együttvéve. Egy másik ok, hogy a modern CPU-k igyekeznek az utasításokat párhuzamosan végrehajtani utasításpipeline-okon(wd) keresztül.[10] Az XOR technikában az egyes műveletek bemenetei az előző művelet eredményétől függenek, ezért szigorúan egymás után kell végrehajtani őket, ami érvényteleníti az utasításszintű párhuzamosság előnyeit.[11]

Az XOR csere a gyakorlatban az álnevezés[12] miatt bonyolult. Ha megpróbáljuk egy adott hely tartalmát önmagával XOR-cserével felcserélni, az eredmény az, hogy a hely nullázódik, és az értéke elvész. Ezért az XOR-cserét nem szabad vakon használni magas szintű nyelvekben, ha az álnevezés lehetséges. Ez a probléma nem érvényes, ha a technikát assemblyben használják két regiszter tartalmának felcserélésére.

Hasonló problémák fordulnak elő a név szerinti hívással,[13] mint például Jensen eszközében(wd), ahol az i és A[i] felcserélése egy ideiglenes változón keresztül helytelen eredményeket ad az argumentumok összefüggése miatt: a csere a temp = i; i = A[i]; A[i] = temp változón keresztül megváltoztatja az i értékét a második utasításban, ami aztán helytelen i értéket eredményez A[i] számára a harmadik utasításban.

Az XOR csere algoritmus alapelve bármely, a fenti L1-L4 kritériumoknak megfelelő műveletre alkalmazható. Az XOR összeadással és kivonással való helyettesítése különféle, kissé eltérő, de nagyrészt egyenértékű megfogalmazásokat ad. Például:[14]

void AddSwap( unsigned int* x, unsigned int* y )
{
    *x = *x + *y;
    *y = *x - *y;
    *x = *x - *y;
}

Az XOR cserével ellentétben ez a variáció megköveteli, hogy az alapul szolgáló processzor vagy programozási nyelv olyan módszert használjon, mint a moduláris aritmetika vagy a bignum,[15] hogy garantálja, hogy az X + Y kiszámítása ne okozzon hibát egészszám-túlcsordulás(wd) miatt.[16] Ezért a gyakorlatban még ritkábban fordul elő, mint az XOR csere.

Azonban a fenti AddSwap implementációja a C programozási nyelvben mindig működik, még egész szám túlcsordulás esetén is, mivel a C szabvány szerint az előjel nélküli egész számok összeadása és kivonása a maradékosztály szabályait követi, azaz a ciklikus csoportban történik, ahol az unsigned int bitjeinek száma. Valójában az algoritmus helyessége abból következik, hogy az és a képletek bármely Abel-csoportban érvényesek. Ez általánosítja az XOR-csere algoritmus bizonyítását: az XOR az összeadás és a kivonás is az Abel-csoportban (ami a s példányának direkt összege(wd)). Ez nem érvényes a signed int típus esetén (ez az alapértelmezett az int típusnál). Az előjeles egészszám-túlcsordulás egy nem definiált viselkedés a C-ben, így a maradékosztály nem garantált a szabvány által, ami helytelen eredményekhez vezethet.

Az AddSwap műveletsorozata mátrixszorzással fejezhető ki a következőképpen:

Regiszterkiosztás alkalmazása

[szerkesztés | forrásszöveg szerkesztése]

A dedikált swap utasítással nem rendelkező architektúrákon az XOR-swap algoritmus szükséges az optimális regiszterkiosztáshoz, mivel ez elkerüli az extra ideiglenes regisztert.[17] Ez különösen fontos azoknál a fordítóprogramoknál, amelyek statikus egyszeres értékadási formát[18] használnak a regiszterelosztáshoz; ezek a fordítók időnként olyan programokat hoznak létre, amelyeknek két regisztert kell cserélniük, amikor egyetlen szabad regiszter sincs. Az XOR csere algoritmus elkerüli egy extra regiszter lefoglalásának vagy a regiszterek főmemóriába való kibontásának szükségességét.[19] Az összeadás/kivonás változat is használható ugyanerre a célra.[20]

Ez a regiszterallokációs módszer különösen releváns a GPU shader(wd) fordítók számára.[21] A modern GPU architektúrákon a változók kiszórása költséges a korlátozott memória-sávszélesség és a magas memóriakésleltetés miatt, míg a regiszterhasználat korlátozása javíthatja a teljesítményt a regiszterfájl(wd) dinamikus particionálása miatt. Ezért egyes GPU fordítók megkövetelik az XOR swap algoritmust.[22]

  1. ↑ Az első három tulajdonság, valamint az egyes elemek inverzének létezése az Abel-csoport definícióját alkotja. Az utolsó tulajdonság az az állítás, hogy minden elem involúció(wd) (öninverz függvény), azaz másodrendű, ami nem igaz minden Abel-csoportra.
  1. ↑ "The Magic of XOR". Cs.umd.edu. 2014. április 1. dátummal az eredeti címről archiválva. Hozzáférés: 2014. április 2.
  2. ↑ "Swapping Values with XOR". graphics.stanford.edu. Hozzáférés: 2014. május 2.
  3. ↑ A matematikában az inverz elem(wd) fogalma általánosítja a számok ellentétét () és reciprokát ().
  4. ↑ Az elemi mátrix(wd) egy négyzetes mátrix, amelyet az egységmátrixon végzett egyetlen elemi sorművelet alkalmazásával kapunk.
  5. ↑ A síkgeometriában a nyírási leképezés(wd) egy affin transzformáció, amely minden pontot egy rögzített irányban elmozdít egy adott, az adott iránnyal párhuzamos egyenestől való előjeles távolságával(wd) arányos mennyiséggel.
  6. ↑ Eves, Howard (1980). Elementary Matrix Theory (reprint ed.). New York: Dover. 37. o. ISBN 0-486-63946-0. Hozzáférés: 2013. április 24.. We shall find that it is sometimes convenient to subdivide a matrix into rectangular blocks of elements. This leads us to consider so-called partitioned, or block, matrices.
  7. ↑ Dobrushkin, Vladimir. "Partition Matrices". Linear Algebra with Mathematica. Hozzáférés: 2024. március 24.
  8. ↑ A guard(wd) egy logikai kifejezés, amelynek igaznak kell lennie az értékelése során, ha a program végrehajtásának folytatódnia kell a kérdéses ágban. A guard clause (védelmi záradék) korai kilépést biztosít egy szubrutinból, és egy gyakran használt eltérés a strukturált programozástól, eltávolítva egy szintet a beágyazásból és kisebb kódot eredményezve: az if guard { ... } helyére if not guard: return; ... kerül.
  9. ↑ Beck, Kent (1997). "Guard Clause". Smalltalk Best Practice Patterns,. 178–179. o.
  10. ↑ A számítástechnikában az utasítás-pipelining egy olyan technika, amely egyetlen processzoron belüli utasításszintű párhuzamosságot valósít meg. A pipelining célja, hogy a processzor minden részét valamilyen utasítással lefoglalja azáltal, hogy a bejövő utasításokat egymást követő lépések sorozatára ("pipeline"(wd)) osztja, amelyeket különböző processzoregységek hajtanak végre, az utasítások különböző részeit párhuzamosan feldolgozva.
  11. ↑ Amarasinghe, Saman; Leiserson, Charles (2010). "6.172 Performance Engineering of Software Systems, Lecture 2". MIT OpenCourseWare. Massachusetts Institute of Technology. 2015. január 25. dátummal az eredeti címről archiválva. Hozzáférés: 2015. január 27.
  12. ↑ A számítástechnikában az aliasing olyan helyzetet ír le, amelyben a memóriában(wd) lévő adathelyhez a programban különböző szimbolikus neveken keresztül lehet hozzáférni. Így az adatok egyetlen névvel történő módosítása implicit módon módosítja az összes alias névhez tartozó értékeket, amire a programozó nem feltétlenül számít. Ennek eredményeként az aliasing különösen megnehezíti a programok megértését, elemzését és optimalizálását.
  13. ↑ A név szerinti hívás (call by name) egy kiértékelési stratégia, ahol a függvény argumentumai nem értékelődnek ki a függvény meghívása előtt, hanem közvetlenül a függvény törzsébe helyettesítődnek be, majd valahányszor megjelennek a függvényben, kiértékelődnek.
  14. ↑ Warren, Henry S. (2003). Hacker's delight. Boston: Addison-Wesley. 39. o. ISBN 0201914654.
  15. ↑ A számítástudományban az tetszőleges precizitású aritmetika(wd), más néven bignum aritmetika azt jelenti, hogy a számításokat olyan számokon végzik, amelyek pontosságát potenciálisan csak a gazdarendszer rendelkezésre álló memóriája korlátozza.
  16. ↑ Egész szám túlcsordulás akkor fordul elő, amikor egy egész számokon végrehajtott aritmetikai művelet olyan numerikus értéket próbál létrehozni, amely kívül esik az adott számú számjeggyel ábrázolható tartományon – vagy magasabb, mint a maximális, vagy alacsonyabb, mint a minimálisan ábrázolható érték.
  17. ↑ A fordítóprogram optimalizálásában a regiszterallokáció(wd) az a folyamat, amelynek során lokális automatikus változókat(wd) és kifejezéseredményeket rendelünk hozzá korlátozott számú processzorregiszterhez.
  18. ↑ A statikus egyszeres értékadású forma (static single assignment form(wd), SSA) egyfajta köztes reprezentáció, ahol minden változóhoz pontosan egyszer rendelnek hozzá értéket.
  19. ↑ Pereira, Fernando Magno Quintão; Palsberg, Jens (2009). "SSA Elimination after Register Allocation" (PDF). Compiler Construction. Lecture Notes in Computer Science. Vol. 5501. 158–173. o. doi:10.1007/978-3-642-00722-4_12. ISBN 978-3-642-00721-7. Hozzáférés: 2022. április 17.
  20. ↑ Hack, Sebastian; Grund, Daniel; Goos, Gerhard (2006). "Register Allocation for Programs in SSA-Form". Compiler Construction. Lecture Notes in Computer Science. Vol. 3923. 247–262. o. doi:10.1007/11688839_20. ISBN 978-3-540-33050-9.
  21. ↑ A shader(wd) egy számítógépes program, amely kiszámítja a megfelelő fény-, sötétség- és színszinteket egy 3D-s jelenet renderelésekor(wd) – ezt a folyamatot árnyékolásnak(wd) nevezik.
  22. ↑ Abbott, Connor; Schürmann, Daniel. "SSA-based Register Allocation for GPU Architectures" (PDF). Hozzáférés: 2022. április 17.
  • Ez a szócikk részben vagy egészben a XOR swap algorithm című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.