Hlavní navigace

GCC 3.0

26. 6. 2001
Doba čtení: 9 minut

Sdílet

Asi nejzajímavější novinkou měsíce je nová verze GCC (GNU Compiler Collection). Přesto, že byla ohlášena jako jakási "předverze 3.0.0", je GCC 2.95.0 již přes rok stará, a tak zajisté není od věci zmínit se o novinkách a o tom, co způsobilo takové zdržení.

C++

Jedním z hlavních plánů pro verzi 3.0.0 byla podpora jazyka C++ odpovídající oficiálnímu standardu a není asi žádným překvapením, že právě tento bod byl jedním z hlavních důvodů zpoždění. Samotný oficiální standard C++ je relativně nový a jeho uveřejnění předcházela řada odkladů; implementace vyžadovala obrovské úsilí, které vyvinuli zejména firma CodeSourcery a Jason Merill z Red Hatu. Až do verze 2.95 se GCC snažilo odpovídat vývojovým verzím budoucího standardu, což přineslo značné komplikace, jelikož mnoho častí se změnilo natolik, že patřičná implementace v GCC musela být od začátku přepsána.

C++ frontend nyní nejen odpovídá standardu, ale pozornost byla věnována i snížení jeho nároků při používání šablon a inlinovaných funkcí. Přesto, že na triviálních programech se překladač spíše zpomalil, u projektů hojně využívajících šablon došlo k několikanásobnému zrychlení a úsporám paměti. Díky přepisu frontendů většiny jazyků, které nyní udržují kompletní strom funkce (původně docházelo ke generaci mezikódu po každém příkazu), může GCC vkládat funkce na stromové úrovni a obejít tak některé zbytečné kopie. Například nemusí vygenerovat mezikódy všech metod všech instancí šablony, ale vyrobit pouze ty, které jsou opravdu potřeba.

Možná méně potěšující změnou, která je důsledkem cíle maximálně se přiblížit standardu, je fakt, že verze 3.0.0 nepodporuje některá rozšíření oproti standardu, které předchozí verze GCC akceptovaly, a tak musí být některé programy v C++ opraveny. Situace ale snad nebude natolik kritická, zejména díky kontroverznímu kroku firmy Red Hat, která zařadila vývojovou verzi GCC do své distribuce, lze teď očekávat, že většina programů je již opravena.

libstdc++

Podstatnou změnou je také kompletní přepis libstdc++ – standardní knihovny jazyka C++. Jeho cílem je nejen více se přiblížit standardu, ale také opatrně minimalizovat velikost kódu a čas nutný pro kompilaci. Problémem standardní knihovny C++, zejména pak SGI STL, je fakt, že se z velké části skládá z šablon. Ty musí být umístěny v hlavičkových souborech, čímž výrazně zpomalují kompilaci a zvětšují výsledný kód.

Co se úspor kódu a rychlosti kompilace týče, lze dnes mluvit spíše o příslibu do budoucna, jelikož je počítáno s množstvím různých vylepšení překladače, která zatím nebyla implementována, například předkompilované knihovny šablon (pomocí slůvka export) nebo eliminace virtuálních funkcí na úrovni linkeru. Některé věci ale už dnes fungují dobře. Například metody společné pro všechny instance šablony jsou implementovány ve zvláštní třídě, a tak nedochází k duplikaci.

Celkové ale má libstdc++ verze 3 u triviálních staticky linkovaných programů větší režii na velikost kódu než její předchůdkyně. Na druhou stranu se těší slušné kompatibilitě se standardem, v některých případech výrazně rychlejšímu kódu a menší režii na velikost kódu u velkých projektů.

C++ ABI

Jednou z nejpodstatnějších změn pro C++ je implementace nového ABI (Application Binary Interface) – standardu, jak se jednotlivé vymoženosti C++ implementují. Pro architekturu IA-64 byl poprvé v historii navržen standardní interface pro C++ tak, aby šlo linkovat objekty vytvořené různými překladači. Aktivním členem týmu vytvářejícího specifikace je i firma CodeSourcery, která ABI implementovala do GCC jako prvního překladače vůbec.

Ačkoliv je standard sepsán jako věc závislá na architektuře IA-64, ve svém principu obsahuje jenom minimum architekturově závislých prvků. Implementace v GCC tedy funguje tak, že ji až na malé výjimky ostatní platformy zdědí. To snad přinese dlouho očekávanou kompatibilitu mezi jednotlivými verzemi překladače a pokud se ostatní vývojáři překladačů zachovají podobně, i kompatibilitu mezi překladači navzájem.

Nové ABI obsahuje také obrovské množství optimalizací (často dle mého názoru zbytečných). Mezi ty podstatné patří například nový formát tabulek pro doručování výjimek implementovaný Richardem Hendersonem, který je mnohem kompaktnější než jeho předchůdce na i386, kde tabulky často zabíraly více než vlastní kód programu, a navíc je nezávislý na výsledné pozici kódu. To je podstatné u sdílených knihoven. Například u KDE aplikací po zapnutí podpory výjimek implementované v gcc 2.95 čas zavádění programu stoupne několikanásobně.

Podstatným zlepšením je i podpora pro debugování C++ programů. Debugger nyní může například umístit breakpoint na výjimku a velikost debugovacích informací je výrazně menší.

GNU Java Compiler

Další zajímavou novinkou je podpora překladu Javy. Samotný překladač byl zařazen již do verze 2.95, jednalo se však o velmi experimentální program. Ve verzi 3.0.0 je už o poznání lepší. Jeho runtime knihovna byla dovedena do rozumně fungujícího stadia a zařazena do překladače, proto pro překlad javovských programů už nebude potřeba stahovat experimentální prostředí.

Překladač dokáže překládat jak javovský zdrojový kód do javovského bytecode, tak i javovský zdrojový kód nebo javovský bytecode do spustitelného programu. Druhá možnost je i dnes docela ojedinělá a dává tak jednoduchým Javovským programům rychlost programů napsaných v jazyce C. U větších programů dochází ke komplikacím zejména díky pomalejšímu sbírání smetí ve srovnání s nejrychlejšími JIT, které mají většinou mnohem lepší implementaci, než je Boehm garbage collector použitý v GCC. Zpomalení vznikají i při provádění kódu, který nelze předem přeložit (například kódu přeneseného po síti).

C preprocesor

Další novinkou verze 3.0.0 je přepsaný preprocesor. Je zajímavé, že právě preprocesor je jednou z nejkompliko­vanějších částí implementace jazyka C. Musí nejen odpovídat nečekaně spletitému standardu, ale také musí být dostatečně rychlý. Původní preprocesor používaný v GCC, CCCP, napsaný v počátcích GNU projektu Richardem Stallmanem už poněkud zastaral, a tak se stalo nutností jej přepsat. V roce 1995 začal Per Bothner pracovat ne novém preprocesoru, CPPLIB.

Hlavní návrhovou změnou byla podpora integrace preprocesoru do samotného překladače (nebo jakéhokoliv jiného programu), což vede k výraznému zrychlení překladu, zejména u C++, kde se jinak přenáší velký objem dat (vzniklý obrovskými hlavičkovými soubory) mezi odděleným preprocesorem a překladačem. Navíc odpadá duplicitní lexikální analýza ve frontendu překladače.

Cpplib ležela delší dobu ladem, jelikož nedokázala plně nahradit CCCP z důvodu množství drobných odchylek (ne od standardu, ale od původní implementace), které způsobovaly například to, že soubory Imakefile přestaly fungovat.

Nevděčnou práci při opravování těchto problémů a dalším zrychlování a čištění kódu odvedli zejména Neil Booth a Zack Weinberg, ale i mnoho dalších.

Díky integraci preprocesoru do překladače bude také možné kvalitně implementovat předkompilované hlavičkové soubory. Existuje několik nezávislých implementací, proto doufejme, že ta nejlepší z nich si najde cestu do verze 3.1.0.

Podstatná je i revize manuálu k preprocesoru, kterou z velké části provedl Zack Weinberg. Nová verze je o poznání čtivější, přehlednější a zasvětí vás do mnoha temných koutů standardu.

Samotný překladač C

I jazyk C se dočkal nového standardu, C99. GCC implementuje řadu nových rozšíření oproti předchozí verzi, například klíčové slovo __restrict__ umožňující dávat překladači tipy o tom, kdy dva ukazatele nemohou ukazovat na stejné místo, typ bool a některé další vymoženosti. Jiná rozšíření, jako např. typ long long, má GCC už dlouhou dobu. Podpora nového standardu je sice zatím nekompletní, ale vše podstatné funguje. Podobně jako u C++ byla některá rozšíření GCC, která jsou nyní redundantní se standardem, označena za přežitek. U většiny z nich vás GCC bude zatím jen varovat.

Kvalita výsledného kódu

Porovnat kvalitu výsledného kódu je vždy náročné, zejména u GCC, které podporuje velké množstvích různých platforem. Za posledních několik let GCC bohužel ztratilo dobrou pověst v této oblasti. To je způsobeno částečně tím, že jeho návrh zastaral a mnoho moderních optimalizací je nečekaně těžké implementovat, částečně také tím, že na kvalitu kódu byl kladen menší důraz, než na korektnost a nové vymoženosti.

Tato situace byla do značné míry dána typem zákazníků firmy Cygnus – jednalo se zejména o výrobce jednodušších procesorů, kteří potřebovali rychle a levně funkční překladač pro svoji architekturu a na výkon kódu tolik nehleděli.

Situace se ale změnila s růstem popularity Linuxu. Nyní je pro většinu velkých výrobců procesorů podstatné, aby Linux běhal na jejich platformě co nejlépe, a tak jsou ochotni investovat značné finance do vývoje GCC.

Ze spolupráce firel Intel a Red Hat vzniklo několik nových optimalizací určených zejména pro IA-64, jako jsou průchod pro „uhádnutí“ chování podmíněných skoků v programu a jeho přerovnání tak, aby kritická cesta skrz funkci byla rovná, průchod pro přejmenování registrů po alokaci tak, aby se kód lépe scheduloval, převod volání funkce těsně před návratem na skok, průchod pro převod podmíněných skoků na ekvivalentní kód bez skoků apod.

Bohužel, mnoho z těchto optimalizací bylo šito horkou jehlou a často nadělají více škody než užitku. Protože až donedávna chybělo jakékoliv testování výkonu výsledného kódu, zůstaly tyto problémy často bez povšimnutí. Díky tomu je kód generovaný GCC 3.0.0 většinou jenom mírně rychlejší než u verze 2.95.0. Podstatné však je, že není horší, jak se tomu často stávalo u předchozích verzí.

Na asi nejpopulárnější platformě i386 je však situace přece jenom příznivější, jelikož firma Intel si najala Richard Hendersona na přepsání generátoru kódu pro tuto architekturu. Nový generátor kódu je výrazně flexibilnější a umožňuje lépe popsat moderní procesory. Díky tomu běží kód na PentiuPro až Pentiu3 o poznání lépe.

Co se týče optimalizací, i já jsem přispěl svou troškou do mlýna. A tak kromě PentiaPro GCC optimalizuje i pro procesory Athlon, K6 a Pentium. Také jsem opravil řadu problémů v nových optimalizacích, upravil staré tak, aby pracovaly lépe s novým backendem a přidal další optimalizace do backendu.

Také většina vývojářů odvedla obrovskou práci na modernizaci jednotlivých částí překladače, a tak se snad v dohledné době podaří zmodernizovat architekturu natolik, že přestane býti brzdou v implementaci dalších optimalizací. Selhání nových optimalizačních průchodů je velice často způsobeno právě problémy s architekturou. Poslední tři měsíce se také provádí automatické testování kvality kódu, proto se snad u GCC 3.1.0 dočkáme výraznějších změn.

Zrychlení na vývojovém stromě za poslední měsíc jsou podstatná, je tedy vidět, že GCC 3.0.0 má množství nevyužitého potenciálu.

Pro ilustraci jedno velice nevědecké měření a sice porovnání byte benchmarku na GCC 2.95.3 a GCC 3.0.0 na Athlonu 1GHz. Pro kompilaci jsem použil „-O3 -march=athlon -ffast-math -fomit-frame-pointer -funroll-loops“, v případě GCC 2.95.0 -march=pentiumpro místo Athlonu, protože Athlon ještě nepodporuje.

GCC 2.95.0:

BYTEmark (tm) Native Mode Benchmark ver. 2 (3/95)
NUMERIC SORT:  Iterations/sec.: 708.360000  Index: 18.306241
STRING SORT:  Iterations/sec.: 72.581956  Index: 31.904157
BITFIELD:  Iterations/sec.: 143548139.773254  Index: 24.623049
FP EMULATION:  Iterations/sec.: 72.055888  Index: 34.642254
FOURIER:  Iterations/sec.: 12714.995768  Index: 14.397160
ASSIGNMENT:  Iterations/sec.: 6.422736  Index: 24.470364
IDEA:  Iterations/sec.: 1441.075099  Index: 22.048273
HUFFMAN:  Iterations/sec.: 711.757504  Index: 19.779283
NEURAL NET:  Iterations/sec.: 18.261226  Index: 30.898860
LU DECOMPOSITION:  Iterations/sec.: 399.440000  Index: 23.582477
...done...
===========OVERALL============
INTEGER INDEX: 24.511669
FLOATING-POINT INDEX: 21.890525
 (90 MHz Dell Pentium = 1.00)
==============================

GCC 3.0.0

BYTEmark (tm) Native Mode Benchmark ver. 2 (3/95)
NUMERIC SORT:  Iterations/sec.: 785.520000  Index: 20.300297
STRING SORT:  Iterations/sec.: 71.702754  Index: 31.517694
BITFIELD:  Iterations/sec.: 188606041.760000  Index: 32.351905
FP EMULATION:  Iterations/sec.: 83.486707  Index: 40.137840
FOURIER:  Iterations/sec.: 11687.186587  Index: 13.233374
ASSIGNMENT:  Iterations/sec.: 12.683345  Index: 48.323029
IDEA:  Iterations/sec.: 1469.421566  Index: 22.481970
HUFFMAN:  Iterations/sec.: 751.286737  Index: 20.877775
NEURAL NET:  Iterations/sec.: 16.806337  Index: 28.437118
LU DECOMPOSITION:  Iterations/sec.: 484.880000  Index: 28.626756
...done...
===========OVERALL============
INTEGER INDEX: 29.368508
FLOATING-POINT INDEX: 22.084928
 (90 MHz Dell Pentium = 1.00)
==============================

U komplikovanějších testů jsou rozdíly často znatelnější.

Rychlost překladu

Asi nejkomplikovanější je situace na i386, kde nový generátor kódu výrazně zvětšil reprezentaci mezikódu programu, a tak se překlad o poznání zpomalil. Díky některým optimalizacím ale GCC3.0 není v průměru o více než 25 % pomalejší než 2.95.0 a v některých krajních případech (překlad šablon v C++) by mělo být i výrazně rychlejší.

Stabilita

O stabilitě překladače asi rozhodne až zkušenost. Zatím se jedná o nejambicióznější verzi vůbec, která obsahuje množství změn, a tak je logické očekávat problémy. Na druhou stranu se ale systém testování GCC zlepšuje, proto snad problémů nebude příliš mnoho. GCC bylo testováno také kompilací hlavních freesoftwarových balíků (jako např. jádro Linuxu).

root_podpora

Disclaimer

Omlouvám se za všechny změny, o kterých jsem se zapomněl zmínit, a pravděpodobné chybičky v článku. Přes to, že se na vývoji GCC aktivně podílím, je náročné vše sledovat do podrobností.

Byl pro vás článek přínosný?

Autor článku