Exisuje nějaká studie, kde by se na *reálných* aplikacích ukázalo, že Thumb je výhodnější v porovnání s původním RISCem? Mě se na ARMu líbila právě ta původní sada, kde každá instrukce mohla mít podmínkové bity, navíc 16 univerzálních registrů, shifty taky skoro v každé instrukci...
Prostě mě připadne, že sice Thumb má poloviční instrukce, ale zato jich bude víc (no jako u nepovedených původních Intelů 8086 - samé MOVy jen proto, že je málo registrů a nejsou univerzální).
Rozbory množství potřebné Flash pro stejně velký program/stejný zdrojový kód jsem viděl mnohokrát od různých výrobců čipů a rozdíly jsou zásadní.
První co na mě vykouklo při hledání je i z Wikipedie odkazovaný starší (2009) článek
http://web.eece.maine.edu/~vweaver/papers/iccd09/iccd09_density.pdf
je tam vidět jak příšerně kódovaná dvouoperandová sada x86 často válcuje všechny RISC. Bude to i dost tím, že kompilátory x86 dostaly asi nejvíce člověkoroků péče a financí. Ale ARM i PPC vychází na délku stejně špatně. Thumb to pak srovnává na rovinu x86. Někde viděl, že Thumb-2 díky několika plným instrukcím a lepšímu využití dalších registrů, se posunuje ještě dále.
MIPS kvůli tomu přišel nejdříve se sadou MIPS-16 a poté MicroMIPS. Podle blogů spřízněných MIPSu vychází lépe než Thumb, podle analýz ARMu vychází v průměru o 20 a 30% hůře. V každém případě 16 bit sady vychází na délku zásadně lépe než 32-bit. Druhá věc je rychlost pro případ, že cache je relativně velká, dostupná a levná (ale to se jedná o velké procesory). Malé MCU mívají akcelerátor Flash (především pro instrukce) okolo 16 x 16 byte, takže zkrácení kódu až na polovinu znamená i velké zrychlení - Flash je v zásadě pomalá - očekávám, podle manuálů tak 3 až 7 CPU cyklů.
U velkých CPU naopak největším problémem v propustnosti jsou špatně predikované skoky. Ostatní se řeší hlubším pipeliningem a vykonáváním instrukcí ne v pořadí zápisu, ale dostupnosti dat - out of order, register renaming atd. Sám jsem o takovém od the fly pipe build CPU pouze s jednou instrukcí pro propojování dat na funkční uzly uvažoval před 20 lety. Dnes to tak a lépe v podstatě již chodí.
U malých CPU ale zůstává množství bytů načtených z paměti (instrukce i data) pro vykonání daného algoritmu kritickým. Přitom třeba pro Cortex-M0+ při dvoustupňové pipeline v podstatě na skocích záleží málo. I když ITxx by nepatrně pomohlo, ale pro svojí složitost chybí.
Zajímavé jsou kompromisy a vyvážení v architektuře ARMv8 a RISC-V. Určitě je špatné mít každou instrukci podmíněnou. Od toho se utíká, protože to přidává další závislost mezi instrukcemi, ještě k tomu v případě ARM32 na jednom jediném registru. I jen vyhodnocení, že instrukce podmíněná není a její odlišné zpracování něco stojí, ale závislosti těch ostatních a nutnost připravit se na závislost a čekání na příznaky u každé instrukce je zbytečná zátěž. ARMv8 pár podmíněných instrukcí zachovává, RISC-V prohlašuje, že je lepší použít jen skokové instrukce a podle implementace architektury je při zjištění, že se jedná o přeskok jedné nebo dvou instrukcí realizovat jako vykonání nop místo instrukce v pipeline. Celkově velmi dobře komentovaný dokument s popisem návrhu ISA RISC- V velmi dobře strovnává, s čím kdo přišel a jak často původně dobrý nápad nakonec při inovaci implementace architektury je často na překážku. Takže se snaží spíše o jednoduchost a dobrou realizovatelnost
http://riscv.org/download.html#tab_isaspec
O krátký souhrn vývoje a vypíchnutí zajímavostí různých architektur jsem se snažím i v rámci jedné své přednášky
https://cw.fel.cvut.cz/wiki/courses/a0b36apo/lectures/12/start
Připomínky vítané.
Diky za vycerpavajici popis, s temi podminkami je to dneska pravda (ale puvodni ARMy mely jen dva rezy ze?, takze tam to neni tak hrozne). Ta odkazovana studie mi ale mozna neprijde uplne ferova, jsou tam vlastne algoritmy pro praci s retezci nebo bajtstreamem, takze to strasne dobre vychazi i na osmibitovych cipech (Z80 neprekvapi, ale i ma oblibena 6502 se drzi, hmm mohli zkusit i PICy se 12bitovymi a 14bitovymi instrukcemi, ale tezko rict, jestli by meli dost RAM na spusteni programu :-).
Kdyby to byly nejake veci s vicebajtovou aritmetikou, to by bylo uplne jinak.
Jinak - pekne prednasky! Jsem potesen, ze jsou takto volne k dispozici a v rozumnych formatech (ne, neni to uplne obvykle).
Souhlasím s tím, že ta studie není optimální. Viděl jsem takových více, ale většinou přímo od ARMu, MIPSu, FreeScale k M-Core (kdo tento počin dnes ještě zná). Tato je alespoň +/-nezávislá. Ale celkové vyznění bylo relativně podobné. Jakmile se začne počítat a je požadavek na 32-bit čísla, tak 8-bity a čisté 16-bity nemají moc šanci. Nebo mají, ale za cenu toho, že veškeré operace jsou nahrazeny voláním podprogramů a rychlost je ještě řádově nižší.
Další věc je, pokud by se porovnával kód zaměřený hodně na plovoucí řádovou čárku a zcela jiné výsledky by mohly vyjít pro multimediální algoritmy, kde se začnou používat SIMD rozšíření sad instrukcí.
Ale pro malá MCU vychází Thumb/obecně zakrytí 32-bit RISCu 16-bit instrukcemi s možností rozšíření dobře. Zajímavý je pak třeba CISC/RISC MSP430, který má také chytře navrženou sadu nebo H8S (16-bit s 16-bit základním opcode, ale také proměnnou délkou instrukcí).
PS: Dík za poznámku. Přidal jsem zatím k přednášce přímo odkaz na Wikipedii. Jestli to přímo zapracuji nebo jen studenty upozorním na to, že opravdu aktuální data mají hledat jinde, zatím nevím. Pro mě je z pohledu vysvětlení důležitý ten začátek a trend, jestli je to pak 10M nebo 4T je již vlastně jedno a stejně v dnešní době je z toho vlastní procesní jednotka zanedbatelná proti integrované cache.
Puvodni ARMy mely 3 stupnouvou pipeline (fetch-decode-execute), takze jestli rez je ta hranice mezi stupni tak ano, jestli je to ten stupen sam tak ne.
Thumb zmensi kod tak o 30%, coz je IMHO zanedbatelne (staci v kompilatoru zapnout inlining nebo nejake agresivni optimalizace a program se vam zvetsi klidne 2*), proto 30% povazuju za zaokrouhlovaci chybu.
Duvod proc je to na nekterych mikrokontrolerech vyhodne pouzit je pomala flash. Treba AT91SAM7Sxx, coz je ta puvodni A32 architektura s Thumbem ma flash ktera bezi na polovicni frekvenci CPU, ale mezi FLASH a sbernici je vyrovnavaci pamet na 2*32 bitu, ktera se aktivuje pri nacitani Thumb instrukce a predikuje ze dalsi dvojice 16bit intrukci bude tesne za tou prave nactenou, takze si ji zacne pripravovat do toho registru, cimz vetsinou skryje latenci. Takhle pak ten Thumb vyjde i paradoxne rychleji nez A32 mod.
Jinak mam ale A32 taky radsi, je elegantnejsi.
Hlavní důvod proč CISC x86 válcuje konkurenci nejsou kompilátory, ale nadstandardní vrstva u RISC nevídaná a to překlad x86 instrukcí do vnitřních RISC mikro-op. Tím je dosaženo tří věcí najednou: 1) Velké hustoty kódu CISC 2) Generování optimálního RISC kódu pro daný model CPU 3) Vysoké rychlosti exekučních jednotek RISC.
Proto v historii zatím každý pokus krmit CPU rovnou RISC instrukcemi selhal, vzpomeňme na i860, Itanium, PowerPC a další.
Mini poznamecka:
pocet tranzistoru (druhy slajd) lze doplnit z
https://en.wikipedia.org/wiki/Transistor_count
(ale je lepsi si to overit z jinych zdroju :)
Pripominka ke slidu 6: MC68000 dokazala vytizit sbernici jen z poloviny (coz olivnilo treba architekturu Amigy kde se CPU ma liche cykly a blitter sude), pouzit k podpore tvrzeni `nedostatecna rychlost pameti => vykonny CISC soubor' je trochu matouci. Pro 68k byla pamet jeste celkem rychla.
Děkuji za názor. Sám jsem nejvíce z m68k pracoval s 68376 a 68360 a ano načítání paměti na dva takty bylo kritické. Pro kritickou část řídicího algoritmu, který byl napsaný v ASM jsem nakonec používal výhradně ty instrukce, které se vešly do dvou byte, aby načítání (i silnějších) instrukcí nezdržovalo. Pro většinu kódu šlo šikovným ofsetováním proti bázi toto dodržet.
Myšlenka a výklad k slide je spíš takový, že přišla doba, kdy se od ASM a ad-hoc sad začaly sady optimalizovat pro programování v C-čku. Přitom kompilátory byly relativně primitivní, takže bylo výhodné různé C-čkové konstrukce (postinkrementy, adresování v polích a strukturách) ideálně přímo mapovat na instrukce procesoru. Zároveň bylo nutné přidat rozumně přímočarou podporu pro automatické proměnné (i v rekurzi) - u prvních generací (8080) to byl celkem horor, 6802/6502/Z80 již na toto trochu myslely, ale generace kam spadá m68k to dotáhla do komfortního stavu.
Protože načítání a i dekódování instrukcí s natavením uIP do mikrokódu bylo pomalé, tak zmenšení potřebného počtu instrukcí a jejich co nejkratší podoba měla v době bez cache zásadní vliv. Cache z velké části problém s načítáním eliminovala a naopak zjednodušení dekodéru i za cenu delších a více instrukcí bylo cestou k zásadnímu zrychlení.
Co se týče ARM32/Thumb, tak plné instrukce jsou určitě komfortnější. Zkrácení je ale podstatné. Sám jsem to vícekrát testoval, ale již si to přesně nepamatuji. Vzal jsem tedy build našeho SW pro starší NXP LPC2148 který GCC přeloží jak v Thumb tak A32 (na novějších věcech pro Cortex-M to nevyzkouším) a zde je výsledek pro -O2 a starší GCC 4.7.2
A32
text data bss dec hex filename
92260 0 2292 94552 17158 ulad31-app
Thumb
text data bss dec hex filename
65408 0 2292 67700 10874 ulad31-app
S -Os
A32
text data bss dec hex filename
89488 0 2292 91780 16684 ulad31-app
Thumb
text data bss dec hex filename
63144 0 2292 65436 ff9c ulad31-app
Rozdíl je to zásadní, který často rozhoduje o tom, jestli se musí koupit dražší chip s více Flash nebo se kód vejde do stávajícího HW.
Kód je mix podpory komunikačních rozhraní RS-485, SPI, I2C a trochu zpracování signálů z 20 (24) bit převodníků v plovoucí řádové čárce. Zařízení zde
http://pikron.com/pages/products/hplc/ulad_31.html
Publikovaná infrastruktura našich firemních projektů, na které staví i další firmy mých kolegů, zde
Obecně pak na ARM32 vysloveně nemám rád řešení přepínání režimů při přijetí výjimek. Vnořená přerušení s více prioritami jsou katastrofa. Vím o čem mluvím, před lety jsem tento kód opravoval v RTEMSu. V ARMv8 se naštěstí stáhli k řešení bližšímu tomu, co je standard na většině RISC. Cortex-M je z tohoto pohledu pohodový, ale minimálně do M4 neumožňuje ukládat stav uživatelského procesu na systémový zásobník, tím je znemožněné procesor kombinovat s MMU, kdy uživatelský SP může ukazovat na nepřítomnou stránku. Ale pro malé věci je Cortex-M a programování v C pohodové.
Výše uvedené je založené na mých zkušenostech a odhadech, rád si poslechnu i jiné názory a svoje vidění světa si poopravím.
Jak na to koukám v objdumpu, tak zkrácení bude ještě o něco větší, protože jednoduché size započítalo asi 3kB .data do .code a .rodata nemám určenou vůbec. Z Map file mi vychází .rodata na další 2kB. Mezi buildy se moc lišit nebude, takže to chce všude okolo 5kB odečíst. Zkrácení pak vychází okolo těch 30%, což považuji za velmi citelné.
Tak na 30% se shodneme ale rozchazime se v te subjektivni casti -- me to prijde jako nepodstatna uspora.
Samozrejme, ze se to nekdy muze hodit, treba pokud to rozhoduje jestli se vam kod do pameti vejde nebo ne. Ale to uz stejne je asi bezpecnejsi koupit vetsi radic, protoze az pak bude potreba do programu neco doplnit tak by se mohlo stat, ze se do flash nevejde ani s Thumbem.
Takze ja uznavam jako jediny duvod pro thumb to, ze dovoluje pouzit flash bezici na polovicni rychlosti CPU.
30% je hodně podstatná úspora! Teda pro profíka. Hobbybastlerovi to samozřejmě bude jedno. Profíkem myslím někoho, kdo se tím živí, počítá náklady, dělá finanční plán, plánuje výrobu na x let dopředu atd. Když si budu bastlit nějakou kusovku doma, tak si tam můžu dát třeba stokrát předimenzovanou součástku a nepoloží mě to. Ale když něčeho plánuju tisíce kusů, tak 30% je s velikou rezervou rozdíl mezi kolosálním úspěchem a totálním krachem.
Tak teoreticky muze byt vyhoda v tom, ze muses u cenove opravdu citlivych zarizeni zmensit datovou sbernici na 16 bitu a nebude to tak moc bolet, jako u puvodni 32bitove architektury (zarovnani je stejne na 16 bitu, tady problem neni - na rozdil on x86, kde to neresi) a problem to bude "jen" u LOAD/STORE.
Ano, tam je to jasne, pokud jde o aplikaci, kde se nevyplati si delat zakaznicky cip, tak se resi systemove pozadavky (jadro, RAM, (EE)PROM atd.).
Kdovi kde ted klesla navratnost pro vyrobu zakaznickeho cipu, byvalo to nekde u milionu kusu, podle me to dolu nespadlo (protoze MCU maji cimdal vetsi pomer vykon/cena).
ENTER a LEAVE není na implementaci volací konvence, ale na alokaci lokálních proměnných na stacku. Pascal i C++ to dělají naprosto stejně.
ENTER a LEAVE se opustilo z důvodu nutnost provádět je mikrokódem, dneska se totožné akce dělají třemi respektive dvěma instrukcemi a je to rychlejší.
Na pascalovskou volací konvenci se dodělávalo RET s parametrem
Ohledně vítězství s přehledem zvítězila variace pascalovské konvence stdcall a konvence C cdecl se používá už jenom z historických důvodů.
Aha, tak na me ten slide delal jiny dojem.
Taky jsem si myslel ze s tim postincrementem to bylo opacne. Ze ho totiz mel pocitac PDP-11 na kterem K&R vyvinuli Ccko a tak dali do jazyka konstrukci ktera dokazala tu instrukci primo vyuzit. Ze ji pak mela 68k neni divu, kdyz se casto uvadi ze si jeji autori vzali za vzor PDP-11 (hlavne tedy jeho ortogonalitu).
Muzete rozvest jak bylo u 6502 mysleno na promenne na zasobniku? Stack mela jen 256 bytu a dal se tak akorat pouzit pro navratove adresy, pokud se moc nepouzivala rekurze.
Nic jako ldr r1, [sp,#32] ale neumela. I to indexovani bylo tak pomale ze jsem nakonec psal sebemodifikujici kod kdyz jsem chtel neco udelat rychle.
Jinak ty prerusovaci mody me se zrovna docela libi. Vnorena preruseni nepouzivam -- nikdy jsem uplne nepochopil k cemu je to dobre. Pouzivam mikrojadro s deadline schedulerem, takze priority nepotrebuju a preruseni se prelozi na poslani zpravy procesu ktery si ho zaregistroval. Je to pravda trochu pomalejsi nez to udelat primo, ale u realtime aplikaci nejde o rychlost ale o to ze se vsechno stihne driv nez bude pozde. A kdyz chci opravdu delat neco hodne rychle a je to jen jedna vec tak se da pouzit mod rychleho preruseni (FIQ) ktery ma sve vlastni registry, takze se CPU nemusi zdrzovat s obnovovanim stavu. Na 30 MHz CPU se da dosahnout milion preruseni za sekundu s tim ze jeste zbyde zhruba 50% vykonu na beh hlavniho programu (samozrejme prace v preruseni musi byt jednoducha neco jako preci-zapis-inkrementuj-porovnej -- ale obcas se to hodi na emulaci periferie kterou zrovna nemam). Tohle Cortex-M3 IMHO tak rychle nedokaze.
A to se v přerušení psala půlka aplikace? IRQ má být co nejkratší. Konkrétně na DDS:
void DDSHandler() {
DisableInterrupt();
SetDAC(PreparedValue);
RunTask(TSK_DDS_GEN);
EnableInterrupt();
}
a příprava mimo IRQ.
Pro UART stačí:
void UARTHandler(){
PushUartFifo(UART_Read());
RunTask(TSK_PROCESS_UART);
}
Jak do jednoho IRQ štěká další, je to neštěstí z pohledu synchronizace i spolehlivosti. Neúměrně se to natahuje, nezbude u kooperativního multitaskingu čas na dokončení tasku,... Při tom jde oboje nachystat tak, že se skoro nemají šanci potkat.