Optimalizace programu

Optimalizace může probíhat na několika úrovních. Obvykle mají vyšší úrovně větší dopad a v pozdější fázi projektu se hůře mění a vyžadují výrazné změny nebo úplné přepsání, pokud je třeba je změnit. Optimalizace tedy může obvykle probíhat zpřesňováním od vyšších úrovní k nižším, přičemž počáteční zisky jsou větší a je jich dosaženo s menším množstvím práce a pozdější zisky jsou menší a vyžadují více práce. V některých případech však celkový výkon závisí na výkonu velmi nízkoúrovňových částí programu a malé změny v pozdní fázi nebo včasné zohlednění nízkoúrovňových detailů mohou mít nadměrný dopad. Obvykle se v průběhu projektu věnuje určitá pozornost efektivitě – i když se to značně liší -, ale zásadní optimalizace je často považována za zdokonalení, které je třeba provést pozdě, pokud vůbec. U déle trvajících projektů obvykle dochází k optimalizačním cyklům, kdy zlepšení jedné oblasti odhalí omezení v jiné oblasti, a ty jsou obvykle omezeny, když je výkonnost přijatelná nebo když jsou zisky příliš malé či nákladné.

Jelikož je výkon součástí specifikace programu – program, který je nepřiměřeně pomalý, není vhodný pro daný účel: videohra s frekvencí 60 Hz (snímků za sekundu) je přijatelná, ale 6 snímků za sekundu je nepřijatelně trhaných – je výkon zvažován od začátku, aby bylo zajištěno, že systém je schopen poskytnout dostatečný výkon, a rané prototypy musí mít zhruba přijatelný výkon, aby byla jistota, že konečný systém bude (po optimalizaci) dosahovat přijatelného výkonu. To se někdy opomíjí v domnění, že optimalizace může být vždy provedena později, což vede k tomu, že prototypy systémů jsou příliš pomalé – často o řád nebo více – a systémy nakonec selhávají, protože z architektonického hlediska nemohou dosáhnout svých výkonnostních cílů, jako například Intel 432 (1981); nebo systémy, které potřebují roky práce, aby dosáhly přijatelného výkonu, jako například Java (1995), která dosáhla přijatelného výkonu až s HotSpotem (1999). Míra, do jaké se výkon mění mezi prototypem a produkčním systémem, a to, jak je přístupný optimalizaci, může být významným zdrojem nejistoty a rizika.

Úroveň návrhuEdit

Na nejvyšší úrovni může být návrh optimalizován tak, aby co nejlépe využil dostupné zdroje vzhledem k cílům, omezením a očekávanému použití/zatížení. Architektonický návrh systému v drtivé většině ovlivňuje jeho výkonnost. Například systém, který je vázán na latenci sítě (kde je latence sítě hlavním omezením celkového výkonu), by měl být optimalizován tak, aby minimalizoval cesty po síti a v ideálním případě provedl jeden požadavek (nebo žádné požadavky, jako u push protokolu), nikoliv několik cest tam a zpět. Volba návrhu závisí na cílech: při návrhu kompilátoru, pokud je hlavní prioritou rychlá kompilace, je jednoprůchodový kompilátor rychlejší než víceprůchodový (za předpokladu stejné práce), ale pokud je cílem rychlost výstupního kódu, pomalejší víceprůchodový kompilátor tento cíl splní lépe, i když sám trvá déle. Volba platformy a programovacího jazyka se vyskytuje na této úrovni a jejich změna často vyžaduje kompletní přepsání, i když modulární systém může umožnit přepsání pouze některé komponenty – například program v Pythonu může přepsat části kritické pro výkon v jazyce C. V distribuovaném systému se volba architektury (klient-server, peer-to-peer atd.) vyskytuje na úrovni návrhu a může být obtížné ji změnit, zejména pokud nelze synchronizovaně vyměnit všechny komponenty (např, staré klienty).

Algoritmy a datové strukturyUpravit

Při celkovém návrhu přichází na řadu dobrá volba efektivních algoritmů a datových struktur a efektivní implementace těchto algoritmů a datových struktur. Po návrhu ovlivňuje výběr algoritmů a datových struktur efektivitu více než kterýkoli jiný aspekt programu. Obecně lze datové struktury měnit obtížněji než algoritmy, protože předpoklad datové struktury a předpoklady její výkonnosti se používají v celém programu, i když to lze minimalizovat použitím abstraktních datových typů v definicích funkcí a omezením definic konkrétních datových struktur na několik míst.

U algoritmů to spočívá především v zajištění toho, aby algoritmy byly konstantní O(1), logaritmické O(log n), lineární O(n) nebo v některých případech log-lineární O(n log n) na vstupu (v prostoru i čase). Algoritmy s kvadratickou složitostí O(n2) se nedaří škálovat a i lineární algoritmy způsobují při opakovaném volání problémy a obvykle se nahrazují konstantními nebo logaritmickými, pokud je to možné.

Kromě asymptotického řádu růstu záleží na konstantních faktorech: asymptoticky pomalejší algoritmus může být rychlejší nebo menší (protože jednodušší) než asymptoticky rychlejší algoritmus, když oba čelí malému vstupu, což může být případ, který nastává ve skutečnosti. Často hybridní algoritmus poskytne nejlepší výkon, protože tento kompromis se mění s velikostí.

Obecnou technikou pro zlepšení výkonu je vyhnout se práci. Dobrým příkladem je použití rychlé cesty pro běžné případy, která zlepšuje výkon tím, že se vyhýbá zbytečné práci. Například použití jednoduchého algoritmu rozvržení textu pro latinku a přechod na složitý algoritmus rozvržení pouze pro složitá písma, jako je dévanágarí. Další důležitou technikou je ukládání do mezipaměti, zejména memoizace, která zabraňuje nadbytečným výpočtům. Vzhledem k důležitosti ukládání do mezipaměti je v systému často mnoho úrovní ukládání do mezipaměti, což může způsobit problémy s využitím paměti a problémy se správností kvůli zastaralým mezipamětím.

Úroveň zdrojového kóduUpravit

Kromě obecných algoritmů a jejich implementace na abstraktním stroji mohou konkrétní volby na úrovni zdrojového kódu znamenat významný rozdíl. Například na raných kompilátorech jazyka C byl while(1) pro nepodmíněný cyklus pomalejší než for(;;), protože while(1) vyhodnotil 1 a pak měl podmíněný skok, který testoval, zda je pravdivý, zatímco for (;;) měl nepodmíněný skok . Některé optimalizace (jako je tato) mohou v dnešní době provádět optimalizační překladače. To závisí na zdrojovém jazyce, cílovém strojovém jazyce a překladači a může to být jak obtížně pochopitelné nebo předvídatelné, tak se to v průběhu času mění; toto je klíčové místo, kde porozumění překladačům a strojovému kódu může zlepšit výkon. Pohyb kódu ve smyčce a optimalizace návratové hodnoty jsou příklady optimalizací, které snižují potřebu pomocných proměnných a mohou dokonce vést k vyššímu výkonu tím, že se vyhnou obcházení optimalizací.

Úroveň sestaveníUpravit

Mezi úrovní zdrojového kódu a úrovní sestavení lze pomocí direktiv a příznaků sestavení vyladit výkonnostní možnosti zdrojového kódu, respektive překladače, jako je například použití definic preprocesoru pro zakázání nepotřebných softwarových funkcí, optimalizace pro konkrétní modely procesorů nebo možnosti hardwaru nebo předvídání větvení. Systémy distribuce softwaru založené na zdrojových kódech, jako jsou Ports od BSD a Portage od Gentoo, mohou tuto formu optimalizace využívat.

Úroveň kompilaceEdit

Použití optimalizačního překladače má tendenci zajistit, aby byl spustitelný program optimalizován alespoň tak, jak dokáže překladač předpovědět.

Úroveň assembleruEdit

Na nejnižší úrovni může psaní kódu pomocí jazyka assembleru, určeného pro konkrétní hardwarovou platformu, přinést nejefektivnější a nejkompaktnější kód, pokud programátor využije celý repertoár strojových instrukcí. Mnoho operačních systémů používaných ve vestavných systémech bylo z tohoto důvodu tradičně psáno v kódu assembleru. Programy (kromě velmi malých programů) se zřídkakdy píší od začátku do konce v assembleru kvůli časové a finanční náročnosti. Většinou se kompilují z vysokoúrovňového jazyka do assembleru a odtud se ručně optimalizují. Pokud jsou efektivita a velikost méně důležité, mohou být velké části napsány v jazyce vysoké úrovně.

S modernějšími optimalizačními překladači a větší složitostí nejnovějších procesorů je těžší napsat efektivnější kód, než jaký vygeneruje překladač, a jen málo projektů potřebuje tento „konečný“ optimalizační krok.

Velká část dnes napsaného kódu je určena k běhu na co největším počtu strojů. V důsledku toho programátoři a kompilátory ne vždy využívají efektivnější instrukce poskytované novějšími procesory nebo zvláštnosti starších modelů. Navíc kód v assembleru vyladěný pro určitý procesor bez použití takových instrukcí může být na jiném procesoru stále suboptimální a očekává jiné vyladění kódu.

Typicky dnes programátoři místo psaní v assembleru použijí disassembler, který analyzuje výstup kompilátoru a změní vysokoúrovňový zdrojový kód tak, aby jej bylo možné zkompilovat efektivněji, nebo pochopí, proč je neefektivní.

Run timeEdit

Překladače typu just-in-time mohou vytvářet přizpůsobený strojový kód na základě dat za běhu, a to za cenu režie kompilace. Tato technika pochází z prvních motorů regulárních výrazů a rozšířila se díky Java HotSpot a V8 pro JavaScript. V některých případech může být adaptivní optimalizace schopna provádět optimalizaci za běhu přesahující možnosti statických kompilátorů tím, že dynamicky upravuje parametry podle aktuálního vstupu nebo jiných faktorů.

Profile-guided optimization je technika optimalizace kompilace v předstihu (AOT) založená na profilech za běhu a je podobná statické analogii „průměrného případu“ dynamické techniky adaptivní optimalizace.

Samomodifikující se kód se může měnit v reakci na podmínky během běhu za účelem optimalizace kódu; to bylo běžnější u programů v assembleru.

Některé návrhy procesorů mohou provádět některé optimalizace za běhu. Mezi příklady patří Out-of-order execution, Speculative execution, Instruction pipelines a Branch predictors. Překladače mohou programu pomoci tyto funkce procesoru využít, například prostřednictvím plánování instrukcí.

Optimalizace závislé na platformě a nezávislé na platforměUpravit

Optimalizace kódu lze také obecně rozdělit na techniky závislé na platformě a nezávislé na platformě. Zatímco ty druhé jsou účinné na většině nebo všech platformách, techniky závislé na platformě využívají specifické vlastnosti jedné platformy nebo se spoléhají na parametry závislé na jedné platformě nebo dokonce na jednom procesoru. Proto může být nutné napsat nebo vytvořit různé verze téhož kódu pro různé procesory. Například v případě optimalizace na úrovni kompilace jsou techniky nezávislé na platformě obecné techniky (jako je odvíjení smyček, redukce volání funkcí, paměťově úsporné rutiny, redukce podmínek atd.), které podobným způsobem ovlivňují většinu architektur procesorů. Skvělý příklad optimalizace nezávislé na platformě byl ukázán u vnitřní smyčky for, kde bylo pozorováno, že smyčka s vnitřní smyčkou for provede za jednotku času více výpočtů než smyčka bez ní nebo s vnitřní smyčkou while. Obecně slouží ke snížení celkové délky instrukční cesty potřebné k dokončení programu a/nebo ke snížení celkové spotřeby paměti během procesu. Na druhé straně techniky závislé na platformě zahrnují plánování instrukcí, paralelismus na úrovni instrukcí, paralelismus na úrovni dat, techniky optimalizace mezipaměti (tj. parametry, které se na různých platformách liší) a optimální plánování instrukcí se může lišit i na různých procesorech stejné architektury.

.