TakéRegistr CPU, Procesorový registr, RegisterPokročilý

Definice

Registr procesoru je malá paměťová buňka přímo uvnitř jádra CPU, do které se ukládají operandy, mezivýsledky a adresy, se kterými instrukce právě pracuje. Přístup trvá zlomek nanosekundy, řádově rychleji než čtení z cache nebo z operační paměti. Registrů je jen desítky a jejich jména jsou pevnou součástí instrukční sady.

Kategorie: Počítačová architekturaAktualizováno

Nezaměňujte: V databázích a v byznysu znamená „registr“ evidenci záznamů; toto heslo se týká výhradně hardwarového registru uvnitř procesoru.

Proč procesor nepočítá rovnou v paměti

Aritmetická jednotka potřebuje operandy dostupné v témže taktu, ve kterém počítá. Operační paměť tuhle podmínku nesplňuje ani zdaleka: čtení z DRAM stojí stovky taktů, čtení z cache první úrovně jednotky taktů. Registr procesoru je proto vestavěný přímo do jádra, tvořený klopnými obvody a propojený s výpočetními jednotkami krátkými vodiči. Typická instrukce jako sečtení dvou čísel proto pracuje s registry, ne s adresami v RAM.

Cenou za rychlost je počet. Registry zabírají na čipu drahé místo a každý další registr prodlužuje pole adres v kódování instrukce. Architektura x86-64 nabízí šestnáct celočíselných registrů obecného použití, AArch64 jich má jednatřicet. Klasické RISC-V RV32I definuje také jednatřicet zapisovatelných plus registr x0, který vždy čte nulu.

Jaké druhy registrů existují

  • Obecné (general purpose): drží čísla a ukazatele, například rax, rbx na x86-64 nebo x0x30 na ARM.
  • Programový čítač: adresa právě prováděné nebo následující instrukce (rip, pc).
  • Ukazatel zásobníku a rámce: hranice zásobníku volání funkcí.
  • Stavový registr (flags): příznaky nuly, přenosu, přetečení a znaménka, které nastavuje předchozí operace a čte podmíněný skok.
  • Vektorové registry: široká pole pro SIMD, například 512bitové zmm u AVX-512.
  • Řídicí a systémové registry: viditelné jen pro jádro operačního systému, řídí stránkování, přerušení a režim procesoru.

Co s registry dělá kompilátor

Zdrojový kód žádné registry nezmiňuje, přiřazení provádí kompilátor ve fázi zvané alokace registrů. Proměnnou, která se používá v těsné smyčce, drží pokud možno trvale v registru; když jich je málo, přebytek odloží na zásobník (spill) a při dalším použití načte zpět. Klíčové slovo register v jazyce C je dnes jen historický relikt, moderní překladače ho ignorují, protože rozhodují lépe než člověk.

Volací konvence pak určuje, které registry předávají argumenty, který nese návratovou hodnotu a které musí volaná funkce zachovat. Právě tahle dohoda umožňuje slinkovat objektové soubory z různých překladačů.

Registry a přepínání kontextu

Obsah registrů tvoří podstatnou část stavu běžícího vlákna. Když plánovač operačního systému vlákno odloží, uloží registry do struktury v paměti a nahraje registry jiného vlákna. Čím širší registrová sada (vektorové jednotky), tím dražší tahle výměna je.

Fyzických registrů je přitom uvnitř moderního CPU mnohem víc než těch pojmenovaných. Přejmenovávání registrů (register renaming) mapuje architektonická jména na větší fyzickou zásobu, čímž odstraňuje falešné závislosti a umožňuje provádět instrukce mimo pořadí.

Příklady z praxe

  1. Sčítání dvou čísel v assembleru x86-64

    Funkce, která sečte dva celočíselné argumenty, na Linuxu dostane operandy podle volací konvence System V v registrech edi a esi. Překladač proto nesahá do paměti vůbec: sečte registry a výsledek nechá v eax, odkud si ho vyzvedne volající. Celá funkce se vejde do dvou instrukcí.

    ; int add(int a, int b)
    add:
        lea eax, [rdi + rsi]   ; a je v edi, b v esi
        ret                    ; návratová hodnota v eax
  2. Nedostatek registrů ve smyčce (spill)

    Smyčka, která současně drží deset živých proměnných, vyčerpá na x86-64 dostupné obecné registry. Kompilátor část z nich odloží na zásobník a při každé iteraci je znovu načítá, takže se do jinak čistě registrové smyčky vloudí přístupy do paměti. Rozdělení smyčky na dvě jednodušší nebo zmenšení počtu živých proměnných bývá rychlejší než jakákoli mikrooptimalizace samotného výpočtu.

    for (int i = 0; i < n; i++) {
        // s1..s8 se vejdou do registrů
        s1 += a[i]; s2 += b[i]; s3 += c[i]; s4 += d[i];
        // další akumulátory už kompilátor odloží na zásobník
    }

Časté omyly

MýtusKdyž v C napíšu register int i, bude proměnná rychlejší.
Ve skutečnostiKlíčové slovo register je od C99 pouhé doporučení a moderní překladače ho při alokaci registrů zcela ignorují. V C++17 bylo dokonce odstraněno jako platný specifikátor úložné třídy.
MýtusRegistry jsou vlastně jen nejrychlejší úroveň cache.
Ve skutečnostiCache je transparentní: hardware do ní data zavádí sám a program o ní neví. Registry naopak program adresuje explicitně jménem v instrukci a nikdy se nezaplní automaticky, přesun dat do nich a z nich řídí kód.
Mýtusx86-64 má šestnáct registrů, takže víc než šestnáct hodnot naráz procesor neudrží.
Ve skutečnostiŠestnáct je počet architektonických jmen. Fyzických registrů má jádro díky přejmenovávání typicky stovky, takže může mít současně rozpracováno mnoho verzí téhož jména z různých instrukcí.

Časté dotazy

Kolik registrů má běžný procesor?
Počet registrů závisí na instrukční sadě, nikoli na výrobci. Architektura x86-64 definuje šestnáct celočíselných registrů obecného použití plus vektorové a systémové registry, AArch64 jednatřicet celočíselných a dvaatřicet vektorových, RISC-V ve variantě RV32I jednatřicet zapisovatelných a jeden pevně nulový. Uvnitř jádra je ale fyzických registrů podstatně víc, protože přejmenovávání registrů mapuje tato jména na větší zásobu, typicky ve stovkách. Programátor v assembleru vidí jen ta architektonická jména.
Jak velký je jeden registr procesoru?
Šířka registru odpovídá šířce architektury: na 64bitovém procesoru má obecný registr 64 bitů, tedy osm bajtů. Vektorové registry jsou širší, u AVX-512 mají 512 bitů, u ARM NEON 128 bitů. Souhrnná kapacita registrové sady je proto řádově stovky bajtů až jednotky kilobajtů, což je zanedbatelné proti cache i operační paměti. Právě tahle nepatrná kapacita je důvodem, proč musí kompilátor pečlivě rozhodovat, které proměnné v registrech udrží.
Můžu registry ovlivnit z vyššího programovacího jazyka?
Přímý přístup k registrům z Pythonu, Javy nebo běžného C neexistuje, přiřazení řeší kompilátor nebo JIT. Ovlivnit se dá nepřímo: menší počet současně živých proměnných, kratší smyčky a vhodné inlinování snižují tlak na registry a omezují odkládání na zásobník. Když je potřeba plná kontrola, používá se vložený assembler, kompilátorové intrinsiky pro SIMD nebo přímo assembler. U výkonnostně kritického kódu bývá jednodušší podívat se na vygenerovaný assembler než hádat, jak se překladač rozhodl.
Proč se obsah registrů ukládá při přepnutí kontextu?
Registry drží celý okamžitý stav výpočtu: mezivýsledky, adresu následující instrukce, ukazatel zásobníku a příznaky. Když plánovač operačního systému odebere procesoru běžící vlákno, musí tento stav uložit do paměti, jinak by se výpočet po návratu nedal korektně obnovit. Uložení a načtení registrové sady je proto jádrem přepnutí kontextu a jeho hlavní cenou. U procesorů se širokými vektorovými registry se stav zvětšuje, takže operační systémy ukládají vektorovou část jen tehdy, když ji vlákno skutečně použilo.

Zdroje

  1. Processor register(otevře se v novém okně)Wikipedia
  2. Register allocation(otevře se v novém okně)Wikipedia
  3. x64 calling convention(otevře se v novém okně)Microsoft Learn
  4. Using the GNU Compiler Collection (GCC)(otevře se v novém okně)GNU Project

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.