Umenie výberu registrov Intel
Napísal som tento článok pre online časopis Scene Zine. Scene Zine sa stará o demo scénu, ktorá je komunitou digitálneho umenia zameranou na presadzovanie limitov počítačov prostredníctvom mixu hudby, umenia a počítačového programovania. Zvláštna kategória demoscénnych inscenácií, 4K intros, sa zameriava na veľkosť surového súboru konečnej produkcie. Cieľom je umiestniť toľko kvalitnej hudby, grafiky a animácie podľa možnosti do 4096 bajtov. Vyžaduje to vysoko špecializované techniky optimalizácie veľkosti, pretože 4096 bajtov je menej miesta ako dve strany zadaného textu alebo pravá ikona systému Windows XP. Tento článok popisuje niektoré z týchto techník.
Niektorí ľudia komentujú, že chcú vidieť v programe Scene Zine ďalšie odborné programovacie články. Ak chcete napraviť situáciu, tento článok je pre všetkých programátorov montážnych jazykov vonku. Diskutuje o výtvarnom umení výberu, ktoré sa používa vo vašom kóde. Tieto informácie by mali zjednodušiť vaše kódovanie a pomôcť vám napísať menšie rutiny.
Keď inžinieri spoločnosti Intel navrhli pôvodný procesor 8086, mali pre každý register osobitný účel. Keďže vytvorili inštrukčnú množinu, vytvorili mnoho optimalizácií a špeciálnych pokynov na základe funkcie, ktorú očakávali od každého registra. Použitie registrov podľa pôvodného plánu spoločnosti Intel umožňuje, aby tento kód plne využíval výhody týchto optimalizácií. Bohužiaľ, toto sa zdá byť strateným umením. Len málo programátorov vie o celkovom dizajne spoločnosti Intel a väčšina kompilátorov je príliš zjednodušená alebo zameraná na rýchlosť spúšťania, aby správne používala registre. Pochopenie toho, ako sa zapájajú registre a inštrukčné súbory, je však dôležitým krokom na ceste k bezproblémovému kódovaniu veľkostí.
Používanie registrov neustále má iné výhody okrem optimalizácie veľkosti. Rovnako ako používanie dobrých názvov premenných, pomocou konzistentných registrov je kód čitateľnejší. Keď sa používajú správne, majú registre takmer jasné významy ako počítadlo slučiek i vo vyšších jazykoch. V skutočnosti občas označujem svoje premenné v C po registroch x86, pretože názvy registrov sú tak opisné. Pri správnom používaní registra, x86 assembler môže byť takmer rovnako dokumentujúci ako jazyk na vysokej úrovni.
Ďalšou výhodou, ktorú prináša konzistentné používanie registra, je lepšia kompresia. Pri produkciách, ktoré používajú kompresor na zabalenie konečnej zostavy, ako napríklad 4K intros, vytvára viac redundantného kódu vedie k menším baleným rozmerom. Keď kód používa dôsledne registráciu, začínajú sa opakovane zobrazovať rovnaké inštrukcie. To zase zlepšuje kompresný pomer.
Ako prehľad, všetky procesory rodiny x86 majú 8 všeobecných registrov. Registry sú 32 bitov, hoci 16-bitové verzie sú prístupné aj špeciálnym predponou pre jeden bajt. V 16-bitovom režime sa situácia zvráti. Spodné 16 bitov je štandardne prístupné a úplné registre sú prístupné len s prefixom.
Každý názov registra je skutočne skratka. To platí aj pre “abecedné” registre EAX, EBX, ECX a EDX. Nasledujúci zoznam zobrazuje názvy registrov a ich význam:
- EAX – Register akumulátorov
- EBX – Základný registe
- ECX – Register počtu
- EDX – Dátový register
- ESI – Zdrojový index
- EDI – Cieľový index
- EBP – Ukazovateľ základne
- ESP – Ukazovateľ stohu
Okrem všeobecných registrov v plnej veľkosti má procesor x86 aj 8 registrov veľkosti bajtov. Pretože tieto registre mapujú priamo do EAX, EBX, ECX a EDX, väčšina ľudí ich zobrazuje ako súčasť väčších registrov. Z hľadiska množiny inštrukcií sú však 8-bitové registre oddelené entity. Napríklad registre CL a CH zdieľajú žiadny z užitočných vlastností registra ECX. Okrem algoritmov AL a AH nemá žiadny z 8-bitových registrov žiadny osobitný význam v inštrukčnej množine, takže sa v tomto článku neuvádza.
EAX: Akumulátor
Existujú tri hlavné procesorové architektúry: register, stack a akumulátor. V architektúre registrov sa môžu vyskytnúť operácie, ako napríklad pridanie alebo odčítanie medzi ľubovoľnými dvoma ľubovoľnými registrami. V architektúre stohu sa medzi hornou časťou zásobníka a inými položkami na stohu vyskytujú operácie. V architektúre akumulátora má procesor jeden kalkulačný register nazývaný akumulátor. Všetky výpočty sa vyskytujú v akumulátore a ostatné registre slúžia ako jednoduché miesta ukladania dát.
Je zrejmé, že procesor x86 nemá architektúru akumulátora. Má však akumulátorový register: EAX / AL. Hoci sa môže vyskytnúť väčšina výpočtov medzi ľubovoľnými dvoma registrami, inštrukčná sada dáva akumulátorovi osobitné preferencie ako register výpočtov. Napríklad všetkých deväť základných operácií (ADD, ADC, AND, CMP, OR, SBB, SUB, TEST a XOR) má špeciálne jednobajtové opcódy pre operácie medzi akumulátorom a konštantou. Špecializované operácie, ako je násobenie, delenie, rozšírenie značiek a korekcia BCD, sa môžu vyskytnúť iba v akumulátore.
Keďže väčšina výpočtov sa vyskytuje v akumulátore, architektúra x86 obsahuje mnoho optimalizovaných pokynov na presun dát do tohto registra a mimo neho. Na začiatok má procesor šestnásť bajtových veľkokapacitných kódov XCHG na výmenu údajov medzi akumulátorom a iným registrom. Nie sú to príliš užitočné, ale poukazujú na to, ako silne inžinieri spoločnosti Intel uprednostnili akumulátor pred ostatnými registrami. Pre nich bolo lepšie vymeniť údaje do akumulátora, než pracovať s tým, kde to bolo. Ďalšie pokyny, ktoré presúvajú údaje do a z akumulátora, sú LODS, STOS, IN, OUT, INS, OUTS, SCAS a XLAT. A nakoniec, inštrukcia MOV má špeciálnu jednobajtovú opcódu na presun dát do akumulátora z konštantnej pamäte.
Vo svojom kóde sa pokúste vykonávať čo najviac práce v akumulátore. Ako uvidíte, zvyšné sedem univerzálnych registrov existuje predovšetkým na podporu výpočtu, ktorý sa vyskytuje v akumulátore.
EDX: Dátový register
Zo siedmych všeobecných registrov je dátový register EDX najužšie viazaný na akumulátor. Pokyny, ktoré sa zaoberajú príliš veľkými údajovými položkami, ako je násobenie, rozdelenie, CWD a CDQ, uchovávajú najvýznamnejšie bity v dátovom registri a najmenej významné bity v akumulátore. V istom zmysle je register údajov 64-bitové rozšírenie akumulátora. Dátový register tiež zohráva úlohu v pokynoch IO. V tomto prípade akumulátor uchováva dáta na čítanie alebo zápis z portu a dátový register obsahuje adresu portu.
Vo vašom kóde je dátový register najužitočnejší pre ukladanie údajov súvisiacich s výpočtom akumulátora. Podľa mojich skúseností väčšina výpočtov potrebuje iba tieto dva registre na ukladanie, ak sú správne napísané.
ECX: Register počtu
Registr počítačov, ECX, je ekvivalent x86 všadeprítomnej premennej i. Každá inštrukcia súvisiaca s počítadlom v systéme x86 používa ECX. Najzrejmejšie pokyny pre počítanie sú LOOP, LOOPZ a LOOPNZ. Ďalšou protikladovou inštrukciou je JCXZ, ktorá, ako naznačuje názov, skočí keď je počítadlo 0. Register počtu sa tiež objavuje v niektorých bitových posunoch, kde drží počet posunov, ktoré sa majú vykonať. Napokon, register počítačov riadi reťazcové inštrukcie prostredníctvom REP, REPE a REPNE predpony. V tomto prípade počítací register určí maximálny počet opakovaní operácie.
Najmä v democh, väčšina výpočtov sa vyskytuje v slučke. V takýchto situáciách je ECX logickou voľbou pre počítadlo slučiek, pretože na nej nie je vytvorený žiadny iný register. Jediným problémom je, že tento register sa počíta namiesto toho hore, ako v jazykoch vyššej úrovne. Navrhovanie počítania smerom nadol nie je však ťažké, takže je to len nepatrná ťažkosť.
EDI: Cieľový index
Každá slučka, ktorá generuje dáta, musí uložiť výsledok do pamäte a vyžaduje tak pohyblivý ukazovateľ. Cieľový index, EDI, je ukazovateľ. Cieľový index obsahuje implikovanú adresu zápisu všetkých operácií reťazca. Najužitočnejšou reťazcovou inštrukciou, pozoruhodne, je zriedka používaný STOS. STOS kopíruje údaje z akumulátora do pamäte a zvyšuje cieľový index. Táto jednobajtová inštrukcia je dokonalá, pretože konečný výsledok akéhokoľvek výpočtu by mal byť akumulátor v každom prípade a uloženie výsledkov do pohybujúcej sa pamäťovej adresy je bežnou úlohou.
Mnoho kodérov považuje cieľový index za maximálne ďalší úložný priestor. Toto je chyba. Všetky rutiny musia uchovávať údaje a niektorý register musí slúžiť ako ukazovateľ úložiska. Pretože cieľový index je určený pre túto úlohu, jeho používanie na ďalšie ukladanie je odpad. Použite zásobník alebo iný register na ukladanie a používajte EDI ako svoj globálny ukazovateľ zápisu.
ESI: Zdrojový index
Zdrojový index ESI má rovnaké vlastnosti ako cieľový index. Jediný rozdiel je v tom, že zdrojový index je pre čítanie namiesto písania. Hoci všetky rutiny spracovania údajov napíšu, nie všetky sú prečítané, takže zdrojový index nie je univerzálne užitočný. Keď príde čas na použitie, zdrojový index je však rovnako výkonný ako cieľový index a má rovnaký typ inštrukcií.
V prípadoch, keď váš kód nečíta žiadne údaje, je samozrejme možné použiť zdrojový index pre pohodlný úložný priestor.
ESP a EBP: Ukazovateľ stohu a Ukazovateľ základne
Z ôsmich všeobecných účelových registrov sa na pôvodný účel široko používa iba ukazovateľ stackov, ESP a ukazovateľ základne EBP. Tieto dva registre sú srdcom mechanizmu funkcie x86. Keď blok kódu volá funkciu, tlačí parametre a návratovú adresu na zásobníku. Keď je vo vnútri, funkcia nastaví ukazovateľ základne rovný ukazovateľu stohu a potom umiestni svoje vlastné vnútorné premenné na zásobník. Od tohto bodu sa funkcia vzťahuje na svoje parametre a premenné vzhľadom k ukazovateľu základne namiesto ukazovateľa zásobníka. Prečo nie ukazovateľ stohu? Z nejakého dôvodu, režimy ukazovateľov stack mizerné adresovanie. V 16-bitovom režime nemôže byť posun pamäte štvorcovej konzoly vôbec. V 32-bitovom režime sa môže zobraziť v hranatých zátvorkách len pridaním drahého bajtu SIB do opcode.
Vo vašom kóde nie je nikdy dôvod použiť ukazovateľ stackov na iné ako zásobník. Základný ukazovateľ je však určený na grafy. Ak vaše rutiny prechádzajú parametre podľa registrov namiesto stohu (mali by), nie je dôvod kopírovať ukazovateľ stohu do ukazovateľa základne. Základný ukazovateľ sa stáva bezplatným registrom pre čokoľvek, čo potrebujete.
EBX: Základný register
V 16-bitovom režime základný register EBX slúži ako všeobecný ukazovateľ. Okrem špecializovaných registrov ESI, EDI a EBP je to jediný všeobecne použiteľný register, ktorý sa môže zobraziť v pamäti s prístupom do štvorcových konzol (Napríklad MOV [BX], AX). V 32-bitovom svete však môže každý register slúžiť ako pamäťový posun, takže základný register už nie je špeciálny.
Základný register získava svoj názov z inštrukcie XLAT. XLAT vyhľadáva hodnotu v tabuľke pomocou AL ako indexu a EBX ako základne. XLAT je ekvivalentný MOV AL, [BX + AL], čo je niekedy užitočné, ak potrebujete vymeniť jednu 8-bitovú hodnotu za inú z tabuľky (myslite na farebné vyhľadávanie).
Zo všetkých všeobecných registrov je teda EBX jediný register bez dôležitého účelu. Je to dobré miesto pre uloženie extra ukazovateľa alebo výpočtového kroku, ale nie oveľa viac.
Záver
Osem všeobecných účelových registrov v rade procesorov x86 má každý jedinečný účel. Každý register má špeciálne pokyny a opcódy, ktoré spĺňajú tento účel vhodnejšie alebo efektívnejšie. Registre a ich použitie sú stručne uvedené nižšie:
- EAX – Všetky hlavné výpočty prebiehajú v EAX, čo je podobne ako pri špeciálnom registri akumulátorov.
- EDX – Dátový register je rozšírením akumulátora. Je to najužitočnejšie na ukladanie údajov súvisiacich s aktuálnym výpočtom akumulátora.
- ECX – Rovnako ako premenná i v jazykoch vyššej úrovne, počítací register je počítadlo univerzálnej slučky.
- EDI – Každá slučka musí niekde uložiť svoj výsledok a cieľový index smeruje na toto miesto. Pomocou inštrukcie STOS s jedným bajtom na zápis údajov z akumulátora tento register robí dátové operácie oveľa efektívnejšie.
- ESI – V slučkách, ktoré spracovávajú dáta, zdrojový index uchováva umiestnenie vstupného dátového toku. Rovnako ako cieľový index, EDI má pohodlnú inštrukciu na jeden bajt pre načítanie dát z pamäte do akumulátora.
- ESP – ESP je posvätný ukazovateľ stohu. S dôležitými pokynmi PUSH, POP, CALL a RET, ktoré vyžadujú jeho hodnotu, nie je nikdy dobrý dôvod použiť ukazovateľ stackov na čokoľvek iné.
- EBP – Vo funkciách, ktoré ukladajú parametre alebo premenné na zásobníku, ukazovateľ základne drží umiestnenie aktuálneho rámca zásobníka. V iných situáciách je však EBP bezplatný register na ukladanie dát.
- EBX – V 16-bitovom režime bol základný register užitočný ako ukazovateľ. Teraz je úplne zadarmo pre ďalšie úložné miesto.
Ako príklad toho, ako tieto registre zapadajú, je tu náčrt typickej rutiny:
mov
esi, source_address
mov
edi, destination_address
mov
ecx, loop_count
my_loop:
lodsd
;Vykonajte nejaké výpočty s eax tu.
stosd
loop
my_loop
V tomto príklade je ECX počítadlo slučky, ESI ukazuje na vstupné dáta a EDI ukazuje na výstupné dáta. Niektoré výpočty, ako rozmazanie, filtrovanie alebo možno farebné vyhľadávanie sa vyskytujú v slučke pomocou EAX ako premennej. Tento príklad je trochu zjednodušený, ale dúfajme, že ukazuje všeobecnú myšlienku. Skutočná rutina by sa pravdepodobne zaoberala oveľa komplikovanejšími údajmi ako DWORD a pravdepodobne by zahŕňala aj veľa plovoucích bodov.
Na záver, použitie registrov, ktoré Intel plánuje, má niekoľko výhod. V prvom prípade umožňuje váš kód využívať mnohé optimalizácie a špeciálne pokyny. To tiež robí kód čitateľnejší, pretože registre vykonávajú predvídateľné funkcie. Napokon, používanie registrov neustále vedie k lepšej kompresii tým, že podporuje opakujúce sa sekvencie inštrukcií.
Copyright © 2003 William Swanson