Umíme to s Delphi: 115. díl – optimalizace programů

Dnešní článek je zaměřen na dvě hlavní témata: nejprve si popíšeme nástroj Project Manager sloužící ke správě projektů v Delphi a ve druhé části článku se ponoříme do hlubin optimalizace algoritmů. Nebudeme však optimalizovat sami: špinavou práci ponecháme na překladači Delphi. Na experimentu ověříme, jak je optimalizace efektivní a také si ukážeme, do jakých problémů nás může dostat.

Na samotný úvod bych vás, milí čtenáři, rád přivítal v novém roce. Přeji všem, aby byl alespoň tak úspěšný jako rok 2003 a abychom měli vždycky alespoň nějaký důvod k optimismu. Ostatně, už známý, všestranný český génius Jára Cimrman pronesl památnou větu: „Nikdy není tak zle, aby nemohlo být hůř!“ :-)

Rád bych také poděkoval za vaši podporu a za povzbudivé vzkazy, které jsem od některých čtenářů na konci roku obdržel. Jsem rád, že řada z vás je s tímto seriálem spokojena; na druhou stranu vím, že stále existuje značný prostor k dalšímu zlepšování. Pokud budete mít v této souvislosti jakýkoliv námět, poznámku, dotaz nebo připomínku, napište prosím do diskuse nebo přímo na adresu vkadlec@post.cz.

A nyní konečně k Delphi. Nejdříve stručně zopakujeme, co bylo obsahem předchozí části. V minulém dílu seriálu jsme se zabývali problematikou relativních a absolutních paměťových adres a vysvětlili jsme si, jakým způsobem (principiálně) probíhá zavedení programu do operační paměti počítače. Poznali jsme také systémový zavaděč (loader), který je zodpovědný právě za spouštění jednotlivých aplikací a za jejich zavádění do operační paměti.

Druhá část předchozího článku byla věnována překladači Delphi a některým možnostem jeho spouštění. Poznali jsme rozdíl mezi volbami Project – Compile a Project – Build, naučili jsme se kontrolovat syntaxi a ukázali jsme si, že projekty je možné sdružovat do skupin a překládat najednou.

Na tomto místě také dnes navážeme. Podrobněji si totiž popíšeme zajímavý a dobře použitelný nástroj integrovaného prostředí Delphi, kterým je Project Manager. Ve druhé části článku se budeme zabývat jednotlivými volbami nastavení překladače.

Prohlížíme obsah projektu: Project Manager

Project Manager je nástroj integrovaného prostředí Delphi, který je možné vyvolat z hlavní nabídky volbou View – Project Manager, případně příšernou klávesovou zkratkou Ctrl – Alt – F11. Okno Project Manageru zobrazuje informace o stavu otevřeného projektu a o všech souborech, kterými je aktuální projekt tvořen. V případě, že otevřený projekt je součástí nějaké skupiny projektů (viz minulý díl seriálu), zobrazuje Project Manager informace o všech projektech dané skupiny, tedy nikoliv jen o otevřeném projektu.

Spuštěný Project Manager si můžete prohlédnout třeba na následujícím obrázku:

Klepněte pro větší obrázek

Tento obrázek vám dává základní představu o tom, jak Project Manager vypadá, nicméně je nutné přiznat, že projekt, který zrovna tento Project Manager ukazuje, je natolik primitivní (skládá se z jednoho modulu apod.), že si zřejmě neprohlédnete všechny vlastnosti, které Project Manager obecně nabízí.

Project Manager tedy slouží k vizuálnímu zobrazení vztahů, které panují mezi soubory otevřeného projektu. V případě, že klepnete pravým tlačítkem myši na kterýkoliv ze zobrazených souborů, můžete provádět několik užitečných operací – soubor otevřít, přidat nebo odebrat soubory do/z projektu, případně celý projekt přeložit. V závislosti na souboru, na který klepnete pravým tlačítkem, se zobrazí více nebo méně rozsáhlá kontextová nabídka, takže klepnete-li třeba na spustitelný soubor (tedy „hlavní“ soubor projektu), můžete z nabídky vyvolat třeba dialog Project Options, případně si můžete prohlížet zdrojový soubor projektu DPR (View Source) apod.

K dalším možnostem Project Manageru patří třeba práce se skupinami projektů a vůbec přidávání projektů do skupin apod.

Dokumentace Delphi doporučuje používat Project Manager ke všem podstatným úpravám vašich projektů, a upřednostňuje Project Manager před přímou editací zdrojových souborů projektu. Hlavním důvodem je skutečnost, že Delphi automaticky prochází a aktualizuje všechny relevantní a související soubory projektu, zatímco při ruční editaci bychom mohli na některou úpravu někde zapomenout a došlo by k nekonzistenci nebo přímo k chybám.

Uložíte-li nastavení svého pracovního prostředí v Delphi (Save Desktop Settings) při otevřeném Project Manageru, bude se tento nástroj automaticky otvírat s kterýmkoliv projektem. Okno Project Manageru je ukotvitelné (Dockable): můžete na něho klepnout pravým tlačítkem a zaškrtnout Dockable, takže si jej můžete „přišpendlit“ ke kterémukoliv jinému oknu svého uživatelského prostředí. Například na následujícím obrázku si můžete vychutnat vskutku odporné spojení Object Inspectoru a Project Manageru: jsem si jist, že naleznete ergonomičtější rozložení své pracovní plochy, ale pro ilustraci snad následující obrázek poslouží:

Klepněte pro větší obrázek

V případě, že si na Project Manager zvyknete, je možné jej používat jako centrální přístup ke všem nejdůležitějším funkcím a operacím, které s projektem typicky provádíme.

Překladač Delphi: první salva přepínačů

V tomto okamžiku se dostáváme k druhé části dnešního článku. Ta je tvořena překladačem Delphi a především možnostmi jeho nastavení a konfigurace. Jak uvidíte, prostředí Delphi umožňuje poměrně významným způsobem ovlivnit možnost práce svého překladače, takže programátoři mají v ruce dostatek silných nástrojů k zajištění toho, aby Delphi překládalo tak, jak oni pískají.

Kromě překladače můžeme konfigurovat i sestavovací program (Linker), takže výsledek je opravdu zcela v našich rukách. Veškerá nastavení je možné provídět v dialogu Project Options (z hlavní nabídky Project – Options, případně klávesová zkratka Ctrl – Shift – F11).

Nejprve se zaměříme na nastavení překladače. V otevřeném dialogovém okně zvolte záložku Compiler (viz následující obrázek).

Klepněte pro větší obrázek

Než se jednotlivými sekcemi společně projdeme, rád bych zdůraznil ještě jednu maličkost: všechna nastavení, která je možné provádět na této záložce dialogu Project Options, je možné nastavovat také programově, přímo ve zdrojovém kódu. Ptáte se, jak na to? Pozorní čtenáři si možná vzpomenou třeba na 106. díl seriálu, který se zabýval zdroji systému Windows (resources).

Ve 106. díle jsme si kromě jiného vysvětlovali problematiku tzv. direktiv překladače. Direktivy překladače jsou speciální pseudopříkazy, které umisťujeme do zdrojového kódu a kterými ovlivňujeme činnost překladače. Z operací, které jsme již poznali a které jsou zajišťovány pomocí direktiv, jmenujme například přilinkování zdrojů do projektu nebo také vkládání zdrojových souborů INC (viz 113. díl seriálu).

Pokud se rozhodneme nastavovat možnosti překladače programově pomocí direktiv, stačí si zapamatovat jednoduché pravidlo: v případě, že chceme některou možnost zapnout (což by odpovídalo zatržení příslušného zatrhávacího pole v dialogu na předchozím obrázku), použijeme direktivu {$X+}, kde X je písmenko odpovídající příslušnému zatrhávacímu poli (v následujícím textu bude vždy pro každé zatrhávací pole uvedeno). Naopak chceme-li příslušnou možnost deaktivovat, použijeme direktivu {$X-}, kde X je opět odpovídající písmenko.

Tolik na úvod, nyní si postupně vysvětlíme význam jednotlivých sekcí parametrů.

Sekce Code Generation

Zatrhávací políčka této sekce umožňují nastavit způsobe, jakým se bude generovat výsledný zdrojový kód programu. Na výběr máme čtyři různé vlasnosti:

Optimization, odpovídající direktiva $O – Zapíná/vypíná optimalizaci zdrojového kódu, kterou překladač provádí/neprovádí. Pokud tuto možnost zaškrtneme, upraví Delphi zdrojový kód tak, aby výsledný program běžel co nejrychleji a co nejkratší dobu. Překladač Delphi „zná“ celou řadu způsobů, jak toho dosáhnout: „vyháže“ všechny proměnné a funkce, které nejsou potřeba, optimalizuje cykly apod. Program se tak stane efektivnější, protože s sebou „nevleče“ zbytečně mnoho nepotřebných proměnných a dalších neefektivních fragmentů. Tato volba je poměrně praktická, proto se doporučuje ponechávat ji zapnutou, avšak i z tohoto pravidla existují výjimky. Zejména ve fázi ladění bych spíše doporučoval optimalizaci vypnout a zaponout ji až při překládání finální verze (určené k šíření). Ptáte se proč? Ukážeme si krátký příklad. Následující příklad ukáže, kam až člověka může dohnat optimalizace prováděná překladačem:-) Předem podotýkám, že uvedený příklad nebude oplývat příliš velkou funkčností, zato však demonstruje, co všechno nám nečekaná optimalizace může nadrobit. Nejprve uvedu zdrojový kód jednoduché procedury. Předpokládejme nejdříve, že optimalizace je vypnutá (použijeme nastavování pomocí direktivy, proto zapíšeme do zdrojového kódu {$O+} ):

procedure TForm1.Button1Click(Sender: TObject);
var x,y,z: Integer;

begin
  {$O-}  // vypneme optimalizaci
  for x:=0 to 250 do begin
    y := x * 2;
    z := x;
  end;
  ShowMessage(`Hotovo, z = ` + IntToStr(z));
end;

Co tento jednoduchý for-cyklus dělá? Jednoduše provede 250 průchodů a v každém z nich nejprve přiřadí do proměnné y dvojnásobek hodnoty řídicí proměnné x a následně přiřadí do proměnné z hodnotu řídicí proměnné x. Po skončení cyklu vypíše zprávu společně s hodnotou proměnné z.

Cyklus je tedy velmi primitivní a očekáváme jasné a bezproblémové výsledky. Pokud také tento prográmek spustíme, vše proběhne správně a obdržíme také očekávanou hlášku, viz následující obrázek:

Klepněte pro větší obrázek

Nyní zkusíme zapnout optimalizaci, tedy změnit direktivu {$O-} na {$O+}:

procedure TForm1.Button1Click(Sender: TObject);
var x,y,z: Integer;

begin
  {$O+}  // zapneme optimalizaci
  for x:=0 to 250 do begin
    y := x * 2;
    z := x;
  end;
  ShowMessage(`Hotovo, z = ` + IntToStr(z));
end;

Program spustíme a uvidíme, co se stane. Schválně, tipnete si?

Správný odhad samozřejmě zní tak, že se nastane nic, přesněji řečeno, že výsledek běhu bude úplně stejný jako v předchozím případě. Zapnutí optimalizace pochopitelně nemůže způsobit změnu výsledků programu; to by byla vskutku vydařená optimalizace :-)

Výsledek bude tedy opět stejný, viz následující obrázek:

Klepněte pro větší obrázek

Kdo očekával něco jiného, neporozuměl dobře principu optimalizace, protože jejím úkolem je pouze program zefektivnit, ale nikoliv z něj udělat úplně jiný program:-) Ale pro zajímavost bychom se mohli pokusit přesvědčit, zda má optimalizace skutečně viditelné výsledky. Je zřejmé, že optimalizátor má v tomto programu co na práci, protože minimálně nějakou nepoužitou proměnnou ve zdrojovém kódu rozhodně najde. Pokusíme se tedy provést jednoduchý experiment: spustíme 10 000× po sobě uvedený cyklus, a to nejprve s vypnutou a následně se zapnutou optimalizací. Následně srovnáme dosažené výsledky.

Abychom přesně zjistili dobu trvání operace, upravíme mírně zdrojový kód:

procedure TForm1.Button1Click(Sender: TObject);
var x,y,z: Integer;
  StartOp, KonecOp : DWORD;

begin
  {$O-}

  StartOp := GetTickCount;
  for x:=0 to 25000000 do begin
    y := x * 2;
    z := x;
  end;

  KonecOp := GetTickCount;
  ShowMessage(`Operace trvala ` + FloatToStr((KonecOp - StartOp) / 1000) + ` vterin.`);

  ShowMessage(`Hotovo, z = ` + IntToStr(z));
end;

Drobná změna spočívá jen v zavedení pomocných proměnný StartOp a KonecOp a v zavolání funkce GetTickCount (která vrací počet milisekund, jež uplynuly od spuštění Windows). Odečtením údajů před začátkem a po skončení operace (a vydělení 1000) získáme počet sekund, po které operace probíhala. Kromě toho zvýšíme počet průběhů cyklem, aby byl časový rozdíl lépe patrný.

Výsledky jsou shrnuty v následující tabulce:

Neoptimalizovaná verze ({$O-}) Optimalizovaná verze ({$O+})
Doba běhu pro 25000000 průchodů 1.978 s 0.419 s

Je patrné, že zapnutím optimalizace jsme (v podstatě bez jakéhokoliv vlastního přičinění) docílili zrychlení běhu programu o 78% (optimalizovaný program běží jen 22% doby, po kterou běžel neoptimalizovaný program). Ještě jinak řečeno – Delphi dokázalo náš mizerný algoritmus vylepšit o neuvěřitelných 78%. A pak kdo je tady programátor :-)

Přesvědčili jsme se tedy, že zapnutí a vypnutí optimalizace má opravdu význam a že dokáže významným způsobem ovlivnit rychlost a dobu běhu aplikace. Zdálo by se tedy, že opravdu neexistuje žádný důvod, proč optimalizaci vypínat.

Tím se však dostáváme k problémům, které nám optimalizace může přinést. Zkusme si totiž představit situaci, v níž náš program neběží dle očekávání a nedává správné výsledky. Nejedná se samozřejmě o tento program, ale čistě hypoteticky o kterýkoliv jiný program, jež je očekáván naším zákazníkem, učitelem nebo šéfem.

Nezbývá nám tedy, než se pustit do ladění programu. My si tento proces budeme simulovat na našem jednoduchém příkladu. Dejme tomu, že po skončení programu se vypíše špatná hodnota proměnné z. Rozhodneme se ladit tuto část zdrojového kódu, protože se nám zdá, že by hypotetická chyba mohla ležet právě tam:

  for x:=0 to 25000000 do begin
    y := x * 2;
    z := x;
  end;

  ShowMessage(`Hotovo, z = ` + IntToStr(z));

Jakým způsobem se do ladění pustíme? Použijeme k tomu dvou prostředků:

  • na řádku z := x umístíme bod přerušení (Breakpoint): Run – Add Breakpoint – Source Breakpoint. V otevřeném dialogovém okně zapíšeme do pole Condition podmínku x = 100 (provádění programu se tedy má zastavit na příslušné řádce v případě, že x je rovno 100, tedy při stoprvním průběhu);
  • aktivujeme dialog Watch: Run – Add Watch. V tomto dialogu si necháme zobrazovat hodnoty proměnný x, y, z. (Přidání proměnné do tohoto dialogu se provede stisknutím klávesy Ins a zapsáním jména požadované proměnné).

Klepněte pro větší obrázek

Nyní program spustíme a ponecháme vypnutou optimalizaci. Po stém průběhu cyklem se provádění programu zastaví a v dialogu Watch čteme následující hodnoty proměnných (viz také následující obrázek):

  • x = 100 (to je logické, takto zněla podmínka pro zastavení provádění programu);
  • y = 200 (to je též logické, do y byl právě přiřazen dvojnásobek x);
  • z = 99 (taktéž v pořádku, v y je dosud předchozí hodnota x přiřazená v předchozím průběhu cyklem).

Pokud klepnete třeba na klávesu F7, provádění programu se posune o příkaz dál a příslušným způsobem se upraví hodnoty proměnné z (nastaví se na 100).

Klepněte pro větší obrázek

Tento průběh je zcela očekávaný a jistě nás nepřekvapí. Zkusme však ponechat bod přerušení i okno Watches beze změn a ve zdrojovém kódu zapnout optimalizaci ({$O+}). Program opět spustíme. Hodnoty proměnných po zastavení jsou velmi odlišné, viz obrázek:

  • x = 100 (to je logické, takto zněla podmínka pro zastavení provádění programu);
  • y = nepřístupné;
  • z = nepřístupné.

Klepněte pro větší obrázek

Proměnné y a z jsou nepřístupné a jejich hodnotu se nedozvíme. V případě proměnné y nás to nepřekvapí, protože její hodnotu nikde v celém programu nečteme (nepotřebujeme), takže ji překladač může s klidem ignorovat a vůbec do ní nic nezapisovat.

Ale proč se nedostaneme k proměnné z, když tu rozhodně nelze považovat za nepoužitou? Přece ji po skončení cyklu přečteme, a dokonce její hodnotu vypisujeme na obrazovku! Navíc podle našeho přesvědčení je problém právě v proměnné z, neboť ta vykazuje po skončení programu špatnou hodnotu! Jak tedy program opravit, když se k proměnné z vůbec nedostaneme?

Vysvětlení situace je následující: při zapnuté optimalizaci je každá proměnná k dispozici jen v těch místech, v nichž existuje nebezpečí, že by mohla být použita. Z toho důvodu není proměnná y k dispozici vůbec, protože překladač dobře ví, že nebude použita nikdy.

Pokud ale klepnete na klávesu F7 (aby se provádění programu posunulo o příkaz dále), dojde k přiřazení hodnoty x do proměnné z, a v tom okamžiku bude proměnná z přístupná, a to až do skončení aktuální iterace.

Předpokládám, že nyní je již zmatení dokonalé. Proč je proměnná z k dispozici vždy od okamžiku svého přiřazení až do skončení aktuální iterace cyklu?

Nehažte flintu do žita, vysvětlení se jistě dobereme. Takže: překladač ví, že proměnnou z budeme potřebovat po skončení cyklu. A také moc dobře ví, kdy tento konec cyklu nastane. Přesněji řečeno – ví, že v našem algoritmu může konec cyklu nastat v jednom jediném případě: při testování řídicí proměnné. Překladač tedy ví, že proměnná z musí být k dispozici v okamžiku, kdy se testuje, zda provést další iteraci nebo skončit – a to se děje v první řádce cyklu (v řádce začínající for). V tom okamžiku musí být proměnná z k dispozici, protože kdyby náhodou už nebylo nutné provádět další iteraci, musí být Delphi schopno hodnotu proměnné z používat.

Pokud za úvodní řádku cyklu zapíšete třeba příkaz

    if x = 150 then break;

bude překladač „držet“ hodnotu v proměnné z nejen do doby, kdy se bude vyhodnocovat řídicí proměnná cyklu, ale až do doby, kdy bude vyhodnocen tento příkaz, protože není jisté, že tento příkaz nezpůsobí ukončení cyklu. Jakmile běh programu překročí (v každé iteraci) tento příkaz, dojde opět ke zrušení proměnné z až do okamžiku, kdy se do ní zapíše nová hodnota, protože optimalizátor si je jist, že aktuální hodnotu proměnné z už rozhodně nebude nikdy potřebovat.

Tímto způsobem tedy v zásadě funguje optimalizace prováděná překladačem Delphi. Zjednodušeně řečeno, při zapnuté optimalizaci překladač drží hodnoty jednotlivých proměnných pouze v těch časových intervalech, v nichž existuje nebezpečí, že by mohly být čteny. Jakmile překladač zjistí (na základě všemožných analýz kódu, které provádí), že před nejbližším zápisem do proměnné (tj. před nejbližší změnou její hodnoty) už se určitě proměnná nebude číst (tj. její hodnota už nebude potřeba), jednoduše hodnotu proměnné zapomene a tváří se, jako by proměnná neexistovala. Tím dosahuje výrazných úspor času a prostředků a značně navyšuje efektivitu programů.

Ale také nám při ladění může přinést nejednu těžkou chvilku :-)

Na závěr

Dnešní článek nejprve vysvětlil, jak pracuje a k čemu slouží Project Manager – nástroj pro správu projektů. Následně jsme se zaměřili na optimalizaci zajišťovanou překladačem, na její přínosy a nevýhody. Na příkladu jsme si ukázali, jak tato optimalizace funguje a pochopili jsme principy, podle nichž Delphi rozhoduje, které proměnné bude plnit hodnotami a které nikoliv. Za týden budeme pokračovat dalšími direktivami překladače.

Diskuze (10) Další článek: Jak udržet Windows v bezpečí

Témata článku: Software, Windows, Programování, Options, Fragment, Hlavní soubor, Zajištění toho, Jára Cimrman, Watch, Jednoduchý nástroj, Jednotlivý díl, Optimalizace, Jednoduchý experiment, Časový rozdíl, Druhá sekce, Překladače, Předchozí nástroj, Díl, Následující řádek, Dialog, Špatná podmínka, Project, Otevřené okno, Manager, DEL


Určitě si přečtěte

Kdyby měli železničáři tento superpočítač za 99 dolarů, nepotřebovali by lasery

Kdyby měli železničáři tento superpočítač za 99 dolarů, nepotřebovali by lasery

** Nejmodernější český železniční tunel je prošpikovaný technologiemi ** Za tři tisíce koupíte počítač, který je překoná ** Seznamte se s Nvidia Jetson Nano

Jakub Čížek | 50

Zorin OS 15: Vyzkoušejte další hezký a nenáročný linux pro mamku a taťku

Zorin OS 15: Vyzkoušejte další hezký a nenáročný linux pro mamku a taťku

** Ačkoliv je grafických linuxů plný internet, stále vládnou Windows ** Jeden z nich se jmenuje Zorin OS a nedávno se dočkal aktualizace ** Dělají jej dva kluci z Irska a je fakt hezký

Jakub Čížek | 114



Aktuální číslo časopisu Computer

Speciál o přechodu na DVB-T2

Velký test herních myší

Super fotky i z levného mobilu

Jak snadno upravit PDF