Programátorská odyseaMichal 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 |
Vytlačiť článok
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
none
|