Když vývojář používající operační systém GNU/Linux použije sousloví „překladač céčka“, je téměř stoprocentně jisté, že má na mysli překladač programovacího jazyka C patřící do rodiny GCC (GNU Compiler Collection). Ovšem ve skutečnosti existuje pro Linux větší množství překladačů céčka. Mezi ně patří i zajímavý projekt Tiny C Compiler, jehož největší předností je velká rychlost překladu i možnost použít tento překladač ve funkci interpretru – je jím tedy možné céčkové programy přímo „spouštět“, podobně jako skripty psané v Perlu či BASHi.

Programovací jazyk C

Jedním z nejpopulárnějších v současnosti používaných programovacích jazyků je bezesporu programovací jazyk C navržený Dennisem Ritchiem. Jazyk C se postupně vyvinul z programovacích jazyků BPCL (autor Martin Richards, 1966) a B (autor Ken Thompson, 1970) až do současné podoby, která byla standardizována v normě ISO/IEC 9899:1999 známé pod zkráceným označením C99. Starší, dodnes velmi často používaný standard se jmenuje ISO 9899:1990, tento starší standard je prakticky shodný (až na jiné číslování jednotlivých paragrafů) s normou ANSI C (ANSI X3.159-1989 „Programming Language C“) a zkráceně se označuje C89 či méně často C90. Se všemi třemi zkratkami C89, C90 a C99 se ještě v tomto článku setkáme.

Obrázek 1: Známá fotografie Kena Thompsona a Dennise Ritchieho sedících před minipočítačem PDP. Podle počátečních písmen jejich příjmení se první de facto standardní syntaxe céčka označovala K&R. Mnoho překladačů dodnes syntaxi K&R podporuje, především z toho důvodu, že některé starší (stále udržované) zdrojové kódy jsou v K&R C napsány.

Programovací jazyk C je i přes absenci některých důležitých vlastností (například mu chybí automatický správce paměti či podpora silného typování a práce s objekty) využívaný jak pro tvorbu open source aplikací, tak i v čistě komerční oblasti – nejedná se jen o vývoj aplikací pro desktopy a servery, ale i pro mikrořadiče či digitální signálové procesory (DSP). Céčko je mnohdy využíváno i ve funkci cílového jazyka, do něhož se překládají programy zapsané v některých vyšších programovacích jazycích – vývojáři, kteří překladače těchto jazyků vytváří, se tak nemusí starat například o nízkoúrovňové optimalizace, protože je za ně již naprogramovali vývojáři překladače céčka. Z historického hlediska je zajímavé, že právě tímto způsobem vznikla první verze jazyka C++ (nástroj Cpre), i když moderní překladače C++ jsou již řešeny odděleně.

Obrázek 2: Dobová fotografie Kena Thompsona, který se podílel na vzniku jazyků B a C. Ken Thompson se později věnoval šachovým algoritmům. Vytvořil například databázi „perfektních“ koncovek a podílel se i na vzniku šachových partií mezi (vel)mistry a počítači.

To však není zdaleka vše, protože programovací jazyk C je dodnes důležitý i z toho důvodu, že jak rozhraní jader některých operačních systémů (Linux, Microsoft Windows i dalších systémů), tak rozhraní systémových knihoven bylo navrženo s ohledem na jmenné konvence céčka i s ohledem na možnosti jeho linkeru. V praxi to například znamená, že grafická knihovna OpenGL používá céčkové jmenné konvence i céčkové datové typy, což může uživatelům jiných jazyků, pro něž taktéž existuje podpora pro OpenGL, připadat poněkud neobvyklé. Totéž platí i pro knihovnu GTK+, která je založena na céčku, ale existují pro ni i různá více či méně kvalitní „objektově orientovaná“ rozšíření pro jiné programovací jazyky, například pro Python.

Obrázek 3: Grafy znázorňující vývoj popularity pěti nejpoužívanějších programovacích jazyků v projektech analyzovaných na serverech Ohloh. Z těchto grafů je patrné, že i když popularita céčka postupem času poněkud klesá (pravděpodobně ve prospěch dynamicky typovaných jazyků), stále je v něm napsáno enormní množství programového kódu.

Překladače programovacího jazyka C v Linuxu

Vývoj operačního systému GNU/Linux byl do značné míry ovlivněn, a možná bych si dokonce dovolil říci, že vůbec umožněn tím, že na něj byly portovány nástroje označované souhrnným názvem „GNU Compiler Collection (GCC)“. Tyto nástroje obsahují překladače několika programovacích jazyků, především pak překladače jazyků C, C++, Objective C, Java, Ada a Fortran. Kromě překladačů jsou součástí GCC i další nástroje, zejména preprocesor používaný jazyky C a C++, GNU assembler, linker, nástroj sledující pokrytí kódu testy (gconv) atd. Jedná se o velmi kvalitní nástroje, které navíc podporují obrovské množství různých platforem, operačních systémů a procesorových architektur, samozřejmě včetně procesorů řady x86 a x86_64.

Obrázek 4: Historie vývoje programovacího jazyka C i některých dalších programovacích jazyků, které jsou z céčka buď přímo odvozeny, nebo jsou alespoň inspirovány jeho syntaxí.

Kromě překladače céčka patřícího do skupiny nástrojů GCC (i samotný překladač se jmenuje GCC – GNU C Compiler, což může být poněkud matoucí, je tedy nutné vždy sledovat kontext, ve kterém se o GCC mluví či píše) je možné v operačním systému Linux použít i další překladače. Mezi ně patří například Clang z projektu LLVM, jenž je zajímavý především po technologické stránce. Dále se pak můžeme setkat s překladači komerčních firem, například překladačem vytvořeným společností Intel, který v případě některých typů optimalizací překonává GCC. V neposlední řadě je pak možné v Linuxu (a nutno říci, že nejenom v něm) použít překladač nazvaný „Tiny C Compiler“ (tcc), jehož popisem a porovnáním s GCC se budeme zabývat v následujících kapitolách.

Obrázek 5: Toto již dnes zcela jistě patří do historie vývoje výpočetní techniky a především pak operačních systémů – výpis části zdrojového kódu jádra Linuxu verze 0.01. Linus Torvalds pro vývoj jádra použil programovací jazyk C zkombinovaný s assemblerem, jak je ostatně ve světě Unixu zvykem.

Tiny C Compiler

Tiny C Compiler (tcc) je překladač programovacího jazyka C, který byl původně vytvořen Fabricem Bellardem a nyní se o jeho další vývoj a portaci na nové platformy stará komunita vývojářů, protože se samozřejmě jedná o open-source projekt. Tiny C Compiler v sobě kromě vlastního překladače obsahuje i linker, což znamená, že jeden binární program může sloužit jak pro překlad zdrojových textů (včetně preprocesingu) do objektového kódu, tak pro vytvoření výsledného spustitelného binárního programu. Všechny tři zmíněné funkce jsou implementovány v jediném spustitelném souboru, jehož velikost na platformě x86 nepřesahuje sto kilobajtů, což je například v porovnání s GCC zcela zanedbatelná velikost (dokonce i pouze GNU assembler je v binární podobě větší než celý tcc).

Tiny C Compiler podporuje standard C89/C90 i velkou část standardu C99, a to do té míry, že úpravy zdrojových kódů určených pro GCC většinou nejsou zapotřebí. Největší devizou překladače tcc je blesková rychlost překladu, protože vlastní překlad je jednoprůchodový. Na stránkách tohoto projektu se uvádí, že tcc je přibližně osmkrát rychlejší než překladač GCC (s použitím standardních voleb, tj. bez optimalizací), ovšem jak se můžete dozvědět z dalšího textu, může být tcc v extrémním případě rychlejší zhruba čtyřicetkrát! Na druhou stranu však tcc za většinou ostatních moderních překladačů céčka pokulhává v případě optimalizací prováděných při překladu. O tom, do jaké míry se absence většiny optimalizačních metod projeví na demonstračních příkladech (benchmarcích), si řekneme v navazujících kapitolách.

Obrázek 6: Aby se ze zdrojových textů napsaných v programovacím jazyku C získala binární spustitelná podoba aplikace, je provedeno několik činností, které jsou v klasických nástrojích (GCC) představovány samostatnými aplikacemi. Zdrojový text je nejdříve zpracován preprocesorem (cpp), potom je přeložen nástrojem cc1 (možným mezivýsledkem je zdrojový text v assembleru) a posléze jsou jednotlivé objektové soubory slinkovány (ld). Výsledkem je buď spustitelná aplikace, popř. Statická, či dynamická knihovna. V případě překladače Tiny C Compiler jsou tyto operace provedeny v jednom kroku.

Co to však znamená pro každodenní praxi vývojáře? Podle mého názoru je ideální zkombinovat vlastnosti tcc (doslova blesková rychlost překladu) a GCC (optimalizace) takovým způsobem, že pro samotný vývoj se použije překladač tcc a pro tvorbu výsledné binární podoby programu se naopak využije překladač GCC s příslušnými přepínači (-O3 atd.). Tiny C Compiler lze použít především na architekturách x86 a x86_64, i když existují i verze využitelné na procesorech ARM a DSP čipech řady TMS320C67xx. Všechna měření, jejichž výsledky jsou uvedeny v dalších kapitolách, byly provedeny na počítači s architekturou x86.

Získání, konfigurace a překlad Tiny C Compileru

Vzhledem k tomu, že překladač s Tiny C Compilerem již v dnešní době není součástí standardních balíčků Fedory, provedeme překlad a instalaci ručně, což však není nic složitého, protože se jedná o pouhých šest příkazů a tcc má pouze minimální závislosti. Nejprve je nutné získat tarball (archiv tar) obsahující zdrojové kódy poslední verze překladače:

wget http://download.savannah.nongnu.org/releases/tinycc/tcc-0.9.25.tar.bz2

Následně se zdrojové texty z archivu rozbalí:

tar xvfj tcc-0.9.25.tar.bz2

Měl by vzniknout nový adresář obsahující jak zdrojové texty, tak různé konfigurační skripty. Přejdeme tedy do nově vytvořeného adresáře:

cd tcc-0.9.25

Nyní již můžeme použít klasickou trojici příkazů pro konfiguraci překladu a vytvoření souboru Makefile, vlastní překlad a následnou instalaci:

./configure
make
sudo make install

A teď už zbývá jediné – ověřit si, že se Tiny C Compiler skutečně nainstaloval a lze ho spustit:

tcc -version
tcc version 0.9.25

Programy použité pro porovnání Tiny C Compileru s GCC

Pro porovnání vlastností Tiny C Compileru a překladače céčka, jenž je součástí GCC, jsem zvolil tři diametrálně odlišné zdrojové kódy. Prvním zdrojovým kódem je kód projektu tth (TeX to HTML Translator), který je dostupný na adrese http://hutchinson.belmont.ma.us/tth/tth-noncom/download.html. Tento projekt je v několika ohledech extrémní, protože je uložen v jediném céčkovém zdrojovém kódu o velikosti 1172 kB, který má celkem 28 249 řádků! Překlad takto velkého souboru je pro překladač (přesněji řečeno pro překladač GCC) skutečně tvrdým oříškem, o čemž se ostatně přesvědčíme v další kapitole.

Druhým testovacím programem je generátor fraktálů, který při svém spuštění velmi intenzivně používá operace s hodnotami s plovoucí řádovou čárkou (floating point). V tomto programu se názorně ukáže, jak dobře provádí překladač různé optimalizace – jak optimalizace výpočtů, tak například rozbalení smyček. Kód tohoto programu je dostupný zde: fractal_renderer.c. Zde je k dispozici varianta se zvýrazněnou syntaxí: fractal_renderer.c.html.

Třetím příkladem je program pro řešení Sudoku. Algoritmus řešení je velmi jednoduchý, což však není v tomto případě podstatné, jelikož nás zajímá rychlost překladu a kvalita výsledného kódu. V tomto programu se velmi často používají operace pro práci s prvky dvourozměrných a trojrozměrných polí, takže se zde ukáže, jak dokáže překladač optimalizovat tento typ operací: sudoku_solver.c. Pro prohlédnutí zdrojového kódu tohoto příkladu můžete opět použít variantu se zvýrazněnou syntaxí: sudoku_solver.c.html.

Testujeme rychlost překladu

Překladač Tiny C Compiler je slovy svých tvůrců přibližně devětkrát rychlejší než překladač GCC, a to dokonce za předpokladu, že GCC nebude provádět žádné optimalizace (výchozím stavem je tedy přepínač -O0). O tom, do jaké míry je či není toto tvrzení pravdivé, se ale raději přesvědčíme sami. V následujících třech tabulkách jsou zobrazeny časy překladu pro všechny tři výše zmíněné demonstrační příklady. Nejprve byl překlad proveden s využitím Tiny C Compileru, posléze byl použit GCC bez dalších voleb a poslední dva překlady proběhly taktéž s využitím překladače GCC, ovšem v tomto případě byly použity volby -O3 (optimalizace na rychlost výsledného kódu) a -Os (optimalizace na velikost výsledného kódu).

Překlad aplikace „tth“:

Překlad Čas překladu TCC rychlejší
gcc 21,1 s 42x
gcc -Os 113,2 s 226x
gcc -O3 137,9 s 275x
tcc 0,5 s

Graf 1: Časy překladu aplikace „tth“. Na vertikální osu je vynesen čas v sekundách.

Překlad demonstračního příkladu „fractal_renderer“:

Překlad Čas překladu TCC rychlejší
gcc 0,440 s 7x
gcc -Os 0,604 s 10x
gcc -O3 0,933 s 15x
tcc 0,060 s

Graf 2: Časy překladu demonstračního příkladu „fractal_renderer“. Na vertikální osu je opět vynesen čas v sekundách.

Překlad demonstračního příkladu „sudoku_solver“:

Překlad Čas překladu TCC rychlejší
gcc 0,449 s 12x
gcc -Os 0,896 s 24x
gcc -O3 9,261 s 244x
tcc 0,038 s

Graf 3: Časy překladu demonstračního příkladu „sudoku_solver“. Na vertikální osu je opět vynesen čas v sekundách.

Z tabulek je patrné, že tvůrci Tiny C Compileru jsou ve svých tvrzeních o rychlosti svého překladače ještě zbytečně skromní, protože tcc dokázal přeložit aplikaci „tth“ zhruba čtyřicetkrát rychleji než gcc! Samozřejmě se jedná o dosti mezní případ, který však názorně ukazuje některé přednosti jednoprůchodového překladu (zdá se, že v tomto případě doba překladu roste pouze lineárně s počtem překládaných řádků). U dalších dvou demonstračních příkladů nejsou rozdíly tak patrné (zrychlení je „pouze“ sedminásobné, popř. dvanáctinásobné), a to zejména proto, že se jedná o relativně malé projekty, u nichž se ještě naplno neprojeví rychlost interní překladové smyčky tcc, protože je nutné provést závěrečné slinkování, které je zhruba stejně rychlé (zpracovávají se tytéž sdílené i statické knihovny atd.).

Vliv (absence) optimalizací na rychlost výsledných programů

Další věc, která nás při porovnávání různých překladačů céčka zajímá, je kvalita výsledného spustitelného binárního kódu. Zde je zřejmé, že Tiny C Compiler nemůže dosahovat kvalit GCC, zejména při povolení všech optimalizací, které GCC podporuje. Ovšem zajímavé bude zjistit a vzájemně porovnat kód vygenerovaný Tiny C Compilerem s kódem, který byl vytvořen překladačem GCC bez zapnutí optimalizací. V ideálním případě by kódy mohly být srovnatelně rychlé, ale opět se o tom raději přesvědčíme změřením doby běhu dvou demonstračních příkladů – „fractal_renderer“ a „sudoku_solver“. Výsledky měření, které byly provedeny na 32bitové platformě s mikroprocesorem Athlon T-Bird (900 Mhz), můžete vidět v následující dvojici tabulek:

Překlad Doba běhu (v sekundách) Relativní rychlost oproti tcc
gcc 52 117 %
gcc -Os 36 169 %
gcc -O3 35 174 %
tcc 61 100 %

Graf 4: Doba běhu demonstračního programu „fractal_renderer“ přeloženého různými překladači s různými volbami.

Překlad Doba běhu (v sekundách) Relativní rychlost oproti tcc
gcc 54 115 %
gcc -Os 21 295 %
gcc -O3 14 443 %
tcc 62 100 %

Graf 5: Doba běhu demonstračního programu „sudoku_renderer“ přeloženého různými překladači s různými volbami.

Z předchozích dvou tabulek a grafů vychází, že kód generovaný překladačem Tiny C Compiler je sice na 32bitové platformě x86 nejpomalejší, ovšem rozdíl oproti neoptimalizovanému kódu generovanému s využitím GCC není nijak závratně velký. Když navíc přihlédneme k dobám kompilace, kde tcc jasně vyhrává, je patrné, že Tiny C Compiler není jen další projekt na hraní, ale reálně a kvalitně naprogramovaný nástroj.

Závěrem

Cílem Tiny C Compileru zajisté není konkurovat ve všech oblastech překladači céčka, který je ústřední součástí nástrojů GCC, ovšem v některých případech může být jeho použití výhodné. TCC je možné použít především při vývoji, protože rychlost překladu je tak velká, že u menších aplikací (řádově stovky až tisíce řádků) vlastně ani není nutné program dělit do více modulů a pro překlad používat soubory Makefile (toto rozdělení je samozřejmě užitečné, ovšem mnohdy se již při vývoji provede takový návrh modulů, který se posléze ukáže být nepříliš vhodný). Tiny C Compiler také může mít své opodstatnění při tvorbě „přímo spustitelných C programů“, protože i bez provádění optimalizací je výsledkem práce TCC vždy nativně spustitelný kód, který je proveden mnohem rychleji než různé verze bajtkódu (Python, částečně i Java) či přímo interpretovaného kódu (BASH) – této možnosti se budeme věnovat příště.