Kolik registrů má x86?
To je přece snadné, má jich 16: RAX až R16.
Ano, šestnáct obecných (general purpose) registrů a k tomu pár dalších: zastaralé x87 a MMX, vektorové XMM, YMM a ZMM registry, AVX512 masky, debug registry, segmentové registry, FLAGS a další specializované, kterým nikdy nemůžu porozumět. V budoucnu k nim ještě přibude AMX, pokud tedy kilobajtu paměti stále můžeme říkat registr.
To ale není celá pravda. Ty nahoře vyjmenované jsou architektonické registry definované ISA, které přeložený program může přímo jmenovat a používat. Uvnitř každého out-of-order procesoru, který sjel z výrobních linek za posledních ±20 letech, se nachází mnohem víc fyzických registrů.
Zen 2 z dílny AMD disponuje 180 integer registry a 160 floating point registry o šířce 256 bitů. Intelí Cascade Lake má pod kapotou 180 int a 168 FP registrů.
Důvodem pro bohatou sadu vnitřních registrů neviditelných zvenku je pochopitelně out-of-order exekuce. Aby mohly být některé operace prováděny spekulativně, procesor nemůže starý výsledek, k němuž se možná bude muset vrátit v případě chybných odhadů, jednoduše přepsat. Spekulativní operace zapíše výsledek do jiného fyzického registru, přičemž se tváří, že to je daný architektonický registr v dané spekulativní budoucnosti. O mechanismech out-of-order procesorů jsem tu psal už dřív.
Přemapování je důvod proč přesun z registru do registru má (v určitých případech) nulovou latenci. Operace nikdy nedojde na backend, je vyřízena v jednotce, která má na starosti o mapování logických registrů na fyzické. Změní se pointer, žádná data se nepřesouvají.
Agner Fog nedávno objevil zajímavé chování v procesorech Zen 2, kdy kombinace store + load instrukcí do/ze stejné adresy mají velice malou latenci. Podle dostupných informací to vypadá na něco zcela nového. Zen 2, když zapíše do paměti (resp. přidá data do store queue pro zapsání od L1 cache později), zároveň je zapíše do jednoho fyzického registru a udržuje mapování mezi adresou a registrem. Při čtení sleduje, zdali program nevyžaduje právě tu proměnnou a pokud ano, poskytne ji z registru s nulovou prodlevou, namísto několika taktů, kolik by trvalo čtení z L1 cache.
Jde tedy o jakousi formu virtuálního rozšíření počtu registrů. Určitá malá část paměti se z hlediska výkonu tváří jako další registry. To se hodí, když jich program potřebuje hodně, ale kompilátor je nedokáže (nebo nemá čas, pokud jde o JIT) alokovat na 16 architektonických a musí udělat spill na zásobník.
Materiály AMD naznačují, že podobný mechanismus omezený na paměť zásobníku a registr RSP byl přítomen už v prvním Zenu. („Memory File for Store to Load Forwarding“) V druhé generaci (dle Angerových tabulek) to využijí jen některé jednoduché instrukce (MOV, ADD, SUB, ADC, SBB, INC, DEC, NEG, AND, OR, XOR, NOT), ale jde o obecný mechanismus, který se netýká jen zásobníku. V podstatě jde o cachování 8B paměti v jednom z pár stovek fyzických registrů. Ty jsou na čipu přítomné tak jako tak a ne vždy musejí být ideálně využité. Ne každý kód dokáže spekulovat dvě stě instrukcí dopředu. Tohle může být způsob, jak je využít v případě, kdy by jinak ležely ladem a dále to komplikují odpověď na otázku kolik má x86 registrů.
Poznámky & odkazy:
- Na wikichip je to popsáno jako memory renaming (hledejte memfile).
- další informace v tomto vlákně
- Travis Downs našel, že podobný mechanismus je přítomný i v Ice Lake (řádek
fw_write_read
) - patent Intelu
- Nejde o osamocený výstřelek. Přesouvání dat blíže k akci a do míst, kde jsou potřeba má v mikroarchitektuře dlouhou tradici. Klasický je store buffer bypass / store-to-load forwarding. Procesor při zápisu nechce čekat, než se data dostanou do cache, je odloží do store bufferu. Další čtení se kromě cache musí podívat právě tam, aby přečetl vlastní zápisy. Jiná jádra, ale nemůžou sahat do cizího store bufferu a to je důvod, prože můžou vidět stará data. Když se zápis dostane do cache, je koheretntní a všichni souhlasí na obsahu paměti. Mezi další klasické techniky patří forwarding, kdy výsledek jedné instrukce je přes bypass network přesměrován k následujícím instrukcím, které ho potřebují jako vstup. Nevznikne bublina v pipeline tím, že první instrukce zapisuje do registru na konci pipeline a následující čte na začátku pipeline. Takhle si to mezi sebou je přehodí, jakmile je to možní a nemusí čekat na zápis čtení z matice registrů.
- Mechanismus popsán v Agnerových optimizačních manuálech v kapitole 20.18 Mirroring memory operands.
- How many registers does an x86-64 CPU have?
- (dodatek) Třetí generace Zenů už to nemá, ale Agner Fog při objevil podobné chování při testování Tiger Lake