Marek Černocký
marek@manet.cz 2010, 2013

# Co optimalizujeme?

Když se pouštíme do optimalizace pro GNOME, musíme mít na paměti zaprvé toto: Nezkoušíme udělat lepším program, nýbrž zkoušíme udělat šťastnějším člověka používajícího počítač.

Lepší program dělá lidi šťastnějšími, ale některá vylepšení to ovlivní více, než jiná: Odezva, rychlost spouštění, snadný přístup k příkazům a to, že nebude počítač odkládat data na disk ve chvíli, kdy spustíte více než dva programy.

Tradiční optimalizace řeší takové věci, jako je využití procesoru, velikost kódu, počet kliknutí myší a využití paměti programem. Tento druhý seznam byl zvolen tak, aby měl přímou souvislost s prvně uvedeným, je zde ale několik podstatných rozdílů: Osoba používající GNOME se nestará o druhý seznam, ale zajímá ji ten první. Při optimalizaci programů GNOME budeme snižovat zátěž procesoru, využití paměti a všechny tyto věci, ale je to jen cesta k řešení, ne cíl. Optimalizujeme pro lidi.

# Optimalizujeme

Předchozí oddíl opominul jeden důležitý kvalifikátor: Optimalizovat se dá jen to, co je měřitelné. Nemůžete měřit štěstí. Můžete ale změřit dobu spouštění a zjistit tak, zda jste ji zkrátili. Štěstí pak bude, snad, následovat.

Optimalizace je proces měření, vypilovávání a opětovného měření. To znamená, že první věc, co musíte udělat, je najít způsob, jak změřit to, co chcete optimalizovat. V ideálním případě je výsledkem měření jedno číslo, například čas potřebný k provedení úlohy. Je to váš test výkonu a jde jen o způsob, jak se dovědět, zda jste zvítězili nebo ne. Je velký rozdíl mezi programem, který by měl být rychlý a programem, který je rychlý.

Když máte základní test výkonu, potřebujete najít proč váš kód nedělá věci tak dobře, jak by měl. Svádí to ke zkoumání: podívat se na kód a zkusit vystopovat něco, co vypadá, že by potřebovalo vylepšit. Zpravidla budete neúspěšní. Jediný způsob, jak s jistotou podrobně zjistit co se v programu kazí, je použít profiler.

Obvykle je problém izolovat malý oddíl kódu. Vypíchněte nejhorší místo a nejprve se soustřeďte na něj. Když to uděláte, spusťte znovu profiler a opakujte to. Postupně vytěžíte z každého kroku méně a méně, až do chvíle, kdy se rozhodnete, že je výsledek dostatečně dobrý. Pokud přes úsilí získáte jen 10% zlepšení, je měly byste se přestat zabývat tímto místem.

Nezapomeňte se na to dívat jako na celek. Než například zkoušet zrychlit kousek kódu, zeptejte se sami sebe, jestli jej vůbec potřebujete spouštět. Mohl by být sloučen s jiným kusem kódu? Je možné výsledky předchozích výpočtů uložit a znovu použít? Optimalizace není potřeba, pokud jde o místo, kterého si uživatel nikdy nevšimne. Pokud je to stále horší, možná byl kód již optimalizován a provádí náročné výpočty, aby se nemusely dělat později. Nebo kód neběží izolovaně a nebo vůbec optimalizovat nepůjde.

# Rady

## Základní

Spusťte testování výkonu po každé změně, kterou uděláte v kódu, a udržujte si evidenci všech svých změn a toho, jak test výkonu ovlivnily. To vám umožní napravit chyby a vyvarovat se stejných chyb do budoucna.

Před započetím optimalizace se ujistěte, že je váš kód správný a bez chyb. Tím se vyvarujete oprav a ladění chyb po optimalizaci.

Než začnete optimalizovat detaily, zaměřte se na optimalizaci na vyšší úrovni.

Používejte správné algoritmy. Klasickým příkladem uváděným v knihách je nasazení quick-sort namísto bubble-sort. Takových příkladů existuje celá řada, některé šetří paměť, jiné výkon procesoru. Také se podívejte, co můžete udělat jednodušeji: můžete použít i rychlejší algoritmus než je quick-sort, pokud se připravíte udělat určité kompromisy.

Optimalizace je o kompromisech. Ukládání výsledků do mezipaměti zrychlí výpočty, ale zároveň zvýší použití paměti. Ukládání dat na disk ušetří paměť, ale platíme za to časem potřebným k jejich načtení z disku nazpět.

Ujistěte se, že jste provedli optimalizaci vůči široké paletě vstupních dat. Pokud tak neučiníte, můžete skončit s kusem kódu pečlivě optimalizovaným pro jedno pole, ale ne tak pro ostatní.

Vyvarujte se náročných operací: Spousty malých čtení z disku. Používání velkého množství paměti, které způsobuje odkládání na disk. Vyvarujte se jakýchkoliv ne nutně nezbytných diskových operací, čtení i zápisu. Síť může být také pomalá. Rovněž omezte grafické operace, které potřebují odezvu od X serveru.

## Nástrahy a léčky

Dejte si pozor na vedlejší efekty. Častá je vzájemná vazba mezi různými částmi kódu, takže zrychlení jedné části může způsobit zpomalení jiné.

Když měříte čas kódu, i na systému v klidu ovlivňují čas události mimo program. Používejte průměrnou hodnotu z více spuštění. Pokud je kód velmi krátký, může být měření času také problematické. V takovém případě měřte čas pro 100 nebo 1000 opakovaných spuštění kódu. Když je zaznamenaný čas delší než několik sekund, mělo by to být v pořádku.

Je velmi snadné nechat se zmást profilerem. Existují historky o lidech, kteří optimalizovali čekací smyčku operačního systému, protože tam program trávil většinu času. Neoptimalizujte kód, který nedělá něco, co by zajímalo uživatele.

Pamatujte na zdroje X serveru. Využití paměti vaším programem nezahrnuje pixelové mapy, protože ty jsou uložené v procesu X serveru, ale paměť tak jako tak okupují. Pomocí xrestop se můžete podívat, které zdroje váš program používá.

## Rady pro nízkoúrovňové programování

Když optimalizujete využití paměti, berte na vědomí rozdíl mezi špičkovým využitím a průměrným využitím. Nějaká paměť je skoro vždy alokovaná, což je obvykle špatně. Nějaká je alokovaná jen zřídka, což může být přijatelné. Nástroje, jako je massif, místo toho používají koncept časoprostoru (spacetime), kdy sledují kolik paměti je využito i na jak dlouho je alokována.

Změřte čas zjednodušeného kousku kódu, který dělá jen to, co považujete za podstatné, čímž získáte nejnižší časový limit, kterého může kód dosáhnout. Například, když optimalizujete smyčku, změřte čas prázdné smyčky. Pokud i to zabere příliš mnoho času, jakékoliv mikrooptimalizace vám nepomohou a je potřeba zásadně změnit návrh. Ujistěte se, že překladač v rámci optimalizace smyčku nevyhodí.

Přesuňte kód mimo programové smyčky. Trochu složitější část kódu, která se spustí jednou je mnohem rychlejší, než jednodušší část, která se spouští tisíckrát. Vyvarujte se častému volání pomalého kódu.

Poskytněte překladači co nejvíce rad je možné. Používejte klíčové slovo const. Používejte `G_INLINE_FUNC` pro krátké, často volané funkce. Podívejte se na `G_GNUC_PURE`, `G_LIKELY` a další různá makra glib. Používejte makra namísto klíčových slov specifických pro překladač gcc, abyste zajistili přenositelnost.

Nepoužívejte jazyk assembler. Není přenositelný a ačkoliv může být na jednom procesoru rychlý, nikde není garantováno, že bude rychlý na všech procesorech, které podporují danou architekturu (např. Athlon vs. Pentium 4).

Nepřepisujte stávající knihovní rutiny, pokud si nejste opravdu jistí, že jsou přehnaně pomalé. Řada knihovních rutin, které intenzivně využívají procesor, již byla optimalizována. Naopak, některé knihovní rutiny jsou pomalé, zvláště ty, které provádí systémová volání operačního systému.

Minimalizujte počet vazeb na knihovny. Na čím méně knihoven se bude program vázat, tím rychleji bude startovat. Toto je věc, která se v GNOME dělá obtížně.

## Triky pro vysokoúrovňové programování

Užívejte výhod běhu více věcí. To nemusí nutně znamenat použití více procesorů, může to znamenat i využití času, který uživatel tráví přemýšlením, co hodná dále dělat, k provádění výpočtů předem. Provádějte výpočty během čekání na načtení dat z disku. Využívejte výhod více zdrojů, používejte je všechny naráz.

Švindlujte. Uživatel si pouze musí myslet, že je počítač rychlejší, není podstatné, jestli tomu tak skutečně je. Jde o to, že důležitý je čas mezi příkazem a odpovědí a pokud uživatel dostane co očekává, není podstatné jestli je odpověď vypočítaná dopředu, uložená v mezipaměti nebo bude ve skutečnosti zpracována později v příhodnějším čase.

Provádějte věci v čekací smyčce. Je to jednodušší, než program, který používá plně vícevláknový běh, a přesto můžete provádět věci skrytě před zraky uživatelů. Ale pozor, pokud strávíte příliš času v čekací smyčce, bude program reagovat rychlostí šneka. Nezapomeňte pravidelně předávat řízení zpět do hlavní smyčky.

Když vše ostatní selže, alespoň oznamte uživateli, že provádění bude pomalé a zobrazte ukazatel průběhu. Sice nebude tak šťastný, jako kdybyste mu zobrazili výsledky, ale aspoň bude vědět, že program nezkolaboval a že si může zatím dát kávičku.