Programátorská odysea

Michal Kára

V AR 15 jsem si se zájmem přečetl recenzi na AmigaE. Se zájmem proto, že autor dosti silně, podle mne většinou bezdůvodně, pomlouval Céčko.

Rozhodl jsem se tedy, že se tomu podívám sám a pokusím se o trochu serióznější srovnání než je porovnání zkompilovaných délek He1loWorldu.
Stáhnul jsem si tedy AmigaE 3.2a z Aminetu a provedl několik testů Pro začátek jsem porovnával rychlosti algoritmu QuickSort. Záměrně jsem příliš nestudoval manuál, spoléhaje na proklamovanou podobnost Éčka s Céčkem a Pascalem.

QuickSort
Napsal jsem jej v C a poté jsem se jal konvertovat zdroják do Éčka. Program byl napsán tak, že nejprve pseudonáhodně vygeneroval posloupnost čísel v rozsahu od jedné do tisíce a poté ji seřadil. Začal jsem tedy deklaracemi a generovací rutinou. Při kompilování řádku

DEF Arr:PTR TO INT

mi Éčko řeklo, že nezná funkci „Arr“. To mne udivilo, neboť zde přece „Arr“ definuji jako pole a s funkcí nemá nic společného. Až po chvíli hledání v manuálu mi došlo, že program chápe všechny identifikátory s velkým počátečním jménem jako funkci a nestará se o to, jak byly definovány.
Nyní jsem po převedení všech proměnných na malá písmena program zkompiloval a spustil. Ovšem nagenerovaná čísla byla záporná, což by být neměla.
Nastalo další hledání v dokumentaci a po něm šokující zjištění: Éčko vůbec neumožňuje definovat, zda má být nejvyšší bit proměnné chápán jako znaménkový nebo ne (v Céčku se říká „signed“ a „unsigned“) !
Změnil jsem tedy generující výraz na

tmp : = Mod((seed AND &7FFFFFFF) / 112,1000);

a doufal, že to bude fungovat. Mýlil jsem se. Vyzkoušel jsem si zdrojový text prokrokovat éčkovým debuggerem, ale bylo to marné, vše vypadalo dobře. Zato jsem mimochodem zjistil, že v menu je u funkce „Step In“ napsáno, že se dá invokovat šipkou vlevo, ale ve skutečnosti je to šipka vpravo. Protože debugger neuměl zobrazit program jako zkompilovaný kód v assembleru, byl jsem nucen použít starý dobrý MonAm. Zde se můj údiv jen zvýšil. Program obsahoval mnoho „NOPů“, tedy instrukcí, které nic nedělají, jen zabírají místo a čas. Zřejmě vznikly tím, že si Éčko vyhradilo místo na dlouhé skoky a potom zjistilo, že stačí krátké. To ovšem nebylo nic proti tomu, co bylo dál. Inkriminovaný výraz se totiž zkompiloval jako

move.l d5,d0
andi.l #$7FFFFFFF,d0
divs.w #$70,d0 (!!!)
ext.l d0
move.l d0,-(a7)
pea $3E8.w
bsr Mod (Funkce mod)
lea 8(a7),a7
move.l d0,d7

Tento kód ovšem nemůže dělat to, co má, tedy dvaatřicetibitově vydělit proměnnou „seed“ (v registru d5) stodvanácti. On se tu totiž dělí šestnáctibitově. Poté jsem se dočetl, že chce-li člověk dělit či násobit ve 32 bitech, musí použít ne operátoru dělení, ale speciální funkce. A já myslel, že podobné manýry patří jen do Assembleru.
Pokračoval jsem dál. Zbytek konverze proběhl již poměrně dobře. Jediným dalším nepříjemným překvapením bylo, že Éčko nechtělo zkompilovat predekrement („--proměnná“), ale to jsem obešel.
Pak jsem měl přeložený program a vyzkoušel vlastní třídění. Ovšem program spíše netřídil než třídil. Záhy jsem přišel na to, že chyba je někde na těchto řádcích:

stptr--;
max:=boundstack[stptr--]
min:=boundstack[stptr];

Podíval jsem se tedy MonAmem a zjistil, že se úsek přeložil jako

stptr--;
stptr--;
max:=boundstack[stptr];
min:=boundstack[stptr];

Poté jsem se v manuálu dočetl, že „--“ , ačkoli se píše za proměnnou, trochu nelogicky znamená odečtení před použitím hodnoty (nejde jenom o Céčko, i v assembleru se píše „-(a0)“ a ne „(a0)-“).
Konečně se mi tedy povedlo zdroják zkompilovat, překonvertovat a spustit. Výsledky vidíte v tabulce. Po zkompilování byl výsledný kód Céčka sice skoro čtyřikrát delší než u Éčka, ale poté co jsem z programu pro účely testu rychlosti kódu odstranil tisk hodnot, velikost céčkového kódu se zmenšila asi na 4.5 KB (číslo v závorce). Pro velké programy budou velikosti výsledného kódu v obou jazycích srovnatelné, možná bude Céčko dokonce lepší díky optimalizacím. Bohužel vzhledem k omezením demoverze nebylo možno tuto domněnku ověřit.
Oněch asi 2.5 KB tvořil kód funkce printf(), která je v Céčku dosti mocná a narozdíl od Éčka umí třeba i tisknout reálná čísla, nejenom celá, což je asi nejnáročnější ze všeho. V důsledku omezeného počtu bitů je totiž v reprezentaci reálných jistá nepřesnost. Mějme číslo, které v díky této chybě může být v rozmezí 1.29997 až 1.30023. Rutina na tisk musí být tak chytrá, aby vytiskla 1.3 (což je v rozsahu).
Proto je také srovnání velikostí zkompilovaných velikostí programu HelloWorld zcela nesmyslné, neboť program netvoří ani tak vlastní kód funkce main(), jako spíše knihovní rutiny a startup.
Pro odborníky: QuickSort byl naimplementován nerekurzívní metodou s preferencí kratšího úseku. Medián byl počítán jako průměr prvního a posledního prvku (nešlo ani tak o efektivitu).

Hledání prvočísel
Dále jsem napsal program na hledání prvočísel Eratosthenovým sítem. Při jeho převádění jsem narazil ještě na pár problémů.
Jednak se Éčko neumí vypořádat s kombinacemi WriteF a PrintF. Je to sice tak trochu i záležitost systému, ale ten Flush() by si dát mohlo.
Původně jsem si myslel, že Éčko nemá bitové rotace, než se mi je podařilo v manuálu najít. Proto jsem je napsal v Assembleru. Výsledek vidíte v tabulce pod označením „Éčko s asm“. Poté co jsem použil funkce Éčka, byl výsledek na můj vkus dosti pomalý (viz tabulka). Pohled do zkompilovaného kódu mi ihned prozradil proč. Éčko totiž oba argumenty (číslo k rotaci a počet bitů o který má být výsledek zrotován) uloží na zásobník a poté zavolá rutinu, která je ze zásobníku vyzvedne a zrotuje. To samozřejmě dosti zpomaluje. Což takhle inline funkce?
Pro ilustraci jsem uvedl v závorce čas potřebný ke kompilaci bez optimalizací, což je běžné při ladění. Optimalizace se zapínají až při finální kompilaci.
Pro odborníky: Použil jsem metodu síta a bitové pole.

Trocha polemiky
Nedá mi to, abych nevyslovil svůj názor k některým tvrzením uvedeným v předešlém článku o Éčku.
Diskuse o tom zda jsou vhodnější složené závorky nebo klíčová slova rozděluje programátorský svět na dva tábory již dlouhou dobu. Klíčová slova jsou názorná pro začátečníky, na druhou stranu profesionálové (a já) je nemají rádi, neboť je zajisté rychlejší napsat „}“ než „ENDWHILE“. Co se týče přehlednosti, tak mnohé se vyřeší vhodným odsazováním. A pamatujte, že jedno z pravidel dobrého programování říká, že procedura by měla jen výjimečně svou délkou přesahovat padesát řádek. Na druhou stranu pokud se používají závorky, je program opticky „řidší“ a lépe vyniknou vlastní funkční slova narozdíl od těch pomocných.
Co se týče středníků: Chce to trochu pozornosti nic víc.
Nemyslím si, že nahrazení znaku „%“ zpětným lomítkem při formátování výrazů je to nejlepší. Výrazy jako „ “ jsou jenom jiným zápisem řídících znaků, jejichž primární podoba (v tomto případě konec řádku) má jiný význam a nemůže být přímo použita. Takovéto sekvence zpracuje sám kompilátor a do programu uloží příslušný kód znaku. Naproti tomu sekvence začínající znakem „%“ jsou zpracovávány až při vlastním tisku a říkají například „zde má být číslo“. Takže se tu míchají dvě různé věci, které se sice používají vedle sebe, ale jinak spolu nemají vůbec nic společného.
Systém výjimek je sice hezká věc, ale zase tak potřebný není. Uváděný příklad, kdy se nepovede otevřít knihovnu (okno apod.) se standardně řeší tak, že existuje jedna funkce, jíž se předává chybový kód (nebo text zprávy, na tom nezáleží). Ona provede příslušné akce, podívá se kde je co otevřeného a pozavírá to. Poté vyskočí z programu céčkovou funkcí exit(). Deklarace automaticky se vyvolávajících výjimek při otevření knihovny je dosti špatně přenositelná mezi různými typy počítačových systémů.

Co se mi nelíbilo
Takže abych to shrnul. Celkově na mě Éčko nepůsobí příliš dobrým dojmem. Je to taková všehochuť. Místo toho, aby jazyk měl co nejméně co nejuniverzálnějších jednoduše použitelných prvků, má Éčko množství různých specialit, například zvláštní příkaz na nekonečnou smyčku. To sice není typický příklad, ale dokresluje styl. Autor pravděpodobně neznal rčení „Méně je někdy více“.
Kompilátor má tu nepříjemnou vlastnost, že se na první chybě zastaví a nepokračuje dál. Zvláště nadšeni tím budou začátečníci. Céčko sice kompiluje pomalu, ale zase pokračuje až do úplného konce, nebo nastaveného počtu chyb. Pokud se musí program znovu kompilovat po každé chybě, můře se reálný čas celkově spotřebovaný na ladění Éčkem a Céčkem dosti přiblížit.
Kapitolu samu pro sebe tvoří systém typů proměnných, v tomto případě však spíše netypů. To přináší své výhody a nevýhody. Je to na první pohled jednodušší. Nezkušeným programátorům chvíli trvá než si na typy zvyknou. Dále to umožňuje zkonstruovat jednodušší a rychlejší kompilátor, neboť se nemusí dávat pozor na to, jaký typ se právě používá v kompilovaném výrazu a tím odpadají i typové konverze.
Na druhou stranu se za to platí tvrdou cenou v podobě vykřičníků u reálných čísel. Samostatnou kapitulou je pak fakt, že ačkoli je implicitním typem LONG, výraz „A / B“ znamená pouze šestnáctibitové dělení. To je z hlediska logické struktury jazyka (honosný výraz, co) přímo odporné a zavrženíhodné. Je tu sice pochopitelné, ale to na věci nic nemění. Ostatně, chcete-li vidět člověka šklebícího se odporem, sdělte tento fakt nějakém programátorovi-analytikovi. Úspěch je zaručen.
Zpět k typům. Například nelze definovat vlastní typ. To bych řekněme ještě pochopil, to je spíše kosmetická vada. O tom, že není možno definovat znaménkovost (zda se nejvyšší bit používá na znaménko), což je dosti podstatné, jsem již také mluvil. Ale jsou zde další věci. Například zde není možno definovat ukazatel na funkci, což se hodí pro různé tabulky.
Zábavu také zažijete, pokud se budete pokoušet definovat ukazatel na ukazatel. Nejprve jsem si myslel, že to nejde. Pak jsem ovšem přišel na způsob, který sice trochu připomíná drbání se pravou rukou za levým uchem, ale funguje. Musí se definovat objekt, který obsahuje ukazatel na daný typ, například na LONG. Poté již můžete deklarovat ukazatel na tento objekt, který bude v tomto případě oním ukazatelem na ukazatel. Zlaté céčkové hvězdičky...
Dále jsem postrádal příkaz který by byl obdobou céčkového „continue“. To provede skok na konec cyklu jako kdyby se před něj umístilo návěští. Dá se to v Éčku obejít příkazem „goto“.
Velice užitečné jsou také proměnné lokální v jednom bloku programu (například for-cyklu), neboť zmenšují paměťové nároky a značně usnadňují kompilátoru optimalizaci. Ty Éčko také neumí.
O takových věcech jako makrech, podmíněné kompilaci (ta je velmi užitečná) nebo dokonce podmíněných výrazech si éčkaři mohou nechat jen zdát.
Obrázek dokreslují věci jako tato: Sekvence

DEF a: PTR TO LONG a:=[1,2,3]:

se zkompiluje tak, že uvedené hodnoty (zde 1, 2 a 3) se uloží přímo za instrukci do kódu funkce a obskakují se. O hojnosti NOPů v kódu zde již také byla zmínka.
Nepřenositelnost je hlavním rysem Éčka. Právě díky ní a některým dalším „úpravám“ bylo možno napsat rychlý kompilátor. Ovšem nejsou zde mužné takové věci jako když jsem vzal zdroják v Céčku pod Linux (klon UNIXU) a zkompiloval ho s mírnými úpravami jak pod MS-DOSem, tak i trochu exotickou VMS. To, je operační systém sálového počítače s riskovým procesorem Alpha. Úpravy byly nutné ne ani tak v jazyce, jako spíše částech. které se zabývaly kontrolou z jakého terminálu je uživatel zalogován apod.

Shrnutí
Chcete-li začít s programováním, je Éčku trochu lepší variantou Basicu. Na druhou stranu pokud budete chtít psát něco složitějšího, můžete se dostat do problémů, které bude nutno složitě obcházet. Céčko doporučuji také těm, kteří chtějí mít své programy potenciálně přenosné i na jiné platformy. Asi největší výhodou Éčka bude rychlost kompilace, ale tady pozor, ochot občas platí úsloví „Práce kvapná, málo platná“.

Poznámky k tabulkám
Časy v tabulkách byly měřeny od nahrání programu do paměti do jeho ukončení. Na tom Céčku trochu ztratilo, neboť si samo natahuje z disku linker. U Éčka tvoří natahování z disku asi jednu až dvě sekundy. Proto je nutné tyto časy brát s rezervou.
Příklady byly voleny tak, aby co nejvíce vynikly vlastnosti kompilátoru. Při aplikacích jenž nemají takové nároky na procesor bude rozdíl menší.

QuickSort
Časy jsou pro setřídění 100 000 čísel

Jazyk Kompilace Délka kódu Čas běhu m:s
Céčko: 25.12s 7916b (4560b) 00:44.70
Éčko: 00.60s 1924b 01:12.08

Hledání prvočísel
Vyhledání prvočísel od jedné do miliónu

Jazyk Kompilace Délka kódu Čas běhu m:s
Céčko: 16.60s (10.60s) 7684b (5132b) 01:24.84
Éčko: 00.50s 1413 b 04:03.96
Éčko s asm: 00.50s 1312b 02:25.28


Pozn.: články boli naskenované ako text a preto obsahujú aj zopár chýb. Taktiež neručíme za zdrojové kódy (Asm, C, Arexx, AmigaGuide, Html) a odkazy na web. Dúfame, že napriek tomu vám táto databáza dobre poslúži.

Žiadna časť nesmie byť reprodukovaná alebo inak šírená bez písomného povolenia vydavatela © ATLANTIDA Publishing