Dual-core: proč nemůže uspět
Proč dual-core (nechtít)
Dvoujádrové procesory jsou veřejnosti předkládány jako produkt, který poskytne značné navýšení výkonu. Nemluví se o tom, že to není až tak docela pravda. Pojďme se podívat proč.
Ideálně paralelizovatelná aplikace - Distributed.net client (počítá šifru RC5-72)
Největším (nikoli však jediným) problémem dvoujádrových procesorů je, že se tak nějak nedostává využití pro zpracování dvou vláken současně. Aby zpracování více vláken mělo nějaký efekt, musí se ona vlákna někde vzít. Hlavní problém je v tom, že taková situace zas tak často nenastává. V zásadě můžeme uvažovat o následujících případech:
- 1. Využití v rámci jednoho programu
2. Využití při spuštění více programů
Jeden program
Na začátku článku jsem se zmínil, že drtivá většina programů je tvořena pouze jedním threadem. Proč? Situace by se dala příhodně nazvat "Programátore, snaž se !".
Výuka programování na našich školách se stále soustředí především na jazyk Pascal. V některých pokročilejších institucích se učí objektový Pascal (= Delphi), případně C++. Výuka se však často zaměřuje na základní programátorské algoritmy jako funkční volání, dynamické proměnné, bublinkové třídění atp. Nějaké optimalizace rychlosti se absolutně neřeší, pouze někteří vyučující chtějí, aby byl kód krátký ...v domnění, že bude zároveň nejrychlejší (což mimochodem nemusí být vůbec pravda). Budoucí programátoři nejen že nemají motivaci zajímat se o optimalizace, ale často ani vůbec netuší, jak procesor funguje.
Vzpomínám na jednu spolužačku ze střední školy, která, přestože byla hodnocena jako nejlepší ve skupině, si z výuky Delphi odnesla tolik, že program funguje po spuštění tlačítka Run. O kompilaci slyšela jen z povzdálí a význam tohoto slova jí zůstal skrytý. Další programátorské dovednosti jí měli naučit na Matematicko-fyzikální fakultě Univerzity Karlovy (... kde se již prvním rokem stala členkou "odpadu").
Aby byl nějak využit výkon víceprocesorových počítačů / dual-core procesorů v rámci jednoho programu, je potřeba, aby tento sestával z více threadů - těch, které pak budeme moct poslat procesoru najednou. Standardně je kód tvořen jedním vláknem - aby jich bylo víc, je nutné pro to něco udělat.
Kompilátor = program, který převede high-level programovací jazyk (například Pascal a jeho konstrukce jako "if a=2 then a:=a+1;" ) na strojový kód - sekvenci velmi jednoduchých příkazů, kterým procesor rozumí.
Dnes běžně používané optimalizace kompilátorem netvoří thready, ale pouze optimalizují sled instrukcí v rámci jednoho threadu tak, aby bylo maximálně využito prostředků jednoho jádra (tzv. instrukční paralelismus). To je pro klasické procesory ideální, protože ty mají jen jednu výpočetní část (jedno jádro). Protože tvoření / zánik threadů s sebou nesou jistý pokles výkonu, byla by optimalizace na vícethreadový model pro tyto procesory nežádoucí. Protože prakticky nikdo z domácích uživatelů nemá dvouprocesorový počítač, je to vcelku pochopitelný stav.
Delphi 7 - žádné optimalizace na multithreding
a velmi chabé optimalizace instrukčního paralelismu
U optimalizací zakládajících na instrukčním paralelismu nemají dual-core procesory benefit oproti jednojádrovým. Jedno vlákno můžeme mít krásně optimalizované a zatížit s ním první jádro, mezi tím se ale druhé bude flákat.
Typy programů
Plný multithreding (ideální situace) - program je od začátku do konce tvořen thready, které jsou na sobě prakticky nezávislé a všechny jsou zátěžové (požadují strojový čas procesoru). Výkon dvouprocesorového systému se přibližuje 200% jednoprocesorového.
Charakteristika situace: Cokoli, co lze rozložit na obrovské množství na sobě téměř nezávislých úkonů, kdy všechny mají prakticky stejnou výpočetní náročnost (např. zkoušení dvou klíčů šifry najednou).
Typický reprezentant: Luštění šifer hrubou silou, testy optimální kompresní metody souborů, rendering grafiky, aplikace filtrů v grafických programech.
Částečný multithreading - program je tvořen jedním hlavním threadem a na něj navázanými pomocnými thready. Další thready bývají vytvořeny buďto za účelem vykonávat kód paralelně (tj. zvýšit výkon) nebo protože někteří programátoři takový styl považují za přehlednější. Výkon dvouprocesorového systému je obvykle kolem 95 až 170 procent výkonu jednoprocesorového, podle zatížení jednotlivých threadů a typu dvoujádrového procesoru. Možný nižší výkon než 100% je způsobem problémy při sdílení výsledků (viz. dále) a také režií spojenou s tvořením a zánikem threadů.
Charakteristika situace: Cokoli, co lze rozložit na menší množství na sobě částečně závislých úkonů, jejichž výpočetní náročnost je různá (umělá inteligence a zároveň zobrazení grafiky v počítačové hře).
Typický reprezentant: Práce se zvukem a obrazem (větší efekt na výkon), hry (menší efekt na výkon).
Žádný multithreading - program sestává pouze z jediného threadu. Výkon dvouprocesorového systému je shodný s jednoprocesorovým. Typická situace pro drtivou většinu programů, kde se optimalizacemi nikdo nechtěl trápit.
Bez více threadů současně to prostě nejde. Jenže to je právě onen problém. Optimalizace v tomto případě sestává z toho, že původní kód programu je rozdělen do oblastí, které běží současně (paralelně) - ale to pouze za předpokladu, že je to z hlediska výpočtu možné. Klasický jeden thread usnadňuje programátorovi práci v tom, že to, co je dřív, je zpracováno dřív. Nestane se mu, že některá činnost předběhne jinou a způsobí havárii (příklad ze života: Nemůžeme hledat v šuplíku časopis, dokud šuplík neotevřeme). Celý problém proto spočívá v tom nalézt tyto pasáže a označit je. Označení probíhá jednou z těchto možností:
1. Přes funkce operačního systému (např. Win32) - Složité, ne každý programátor to umí, těžko se obsluhuje.
2. Přes OpenMP direktivy - Zjednodušená syntax, kterou je tvořeno velké množství multithreadových programů. Problém je, že direktivy musí použitý kompilátor znát - běžně používané kompilátory je bohužel neznají.
3. Kompilátorem - Takto je možné vytvořit thready na menší pasáže kódu, obvykle se používají techniky převádění cyklů na vlákna a speculative precomputation.
První dva typy řeší sám programátor. Znamená to, že problém je přenesen na něj, musí hledat a označovat kód. Mimo to programátor musí zaručit, že jednotlivé thready na sebe budou navazovat ve správném pořadí (že napřed otevřeme šuplík) a budou si předávat výsledky. Taková optimalizace ho stojí čas a je potenciálním zdrojem chyb v programech. Ve výsledku je nutné déle programovat a lépe ladit, což zákazníka bude stát více peněz za program. Pokud na vyšší částku nebude ochoten přistoupit, pak jednoduše není jediný důvod, proč by se měl programátor s optimalizováním obtěžovat.
O tom, jaké "nadšení" vyvolávají dual-core procesory, jsem měl možnost přesvědčit se u kamaráda - MI'RA pracuje v jedné velké softwarové firmě a co se programování týče, je to ostřílený veterán. Zkoušel už několik programovacích jazyků (Delphi, Java, C++, Assembler) a jeho záběr je především na programy pro běžné uživatele (kancelářské aplikace) - tedy nejčastěji zastoupenou sortu programů.
Eagle: Umíš programovat multithreadově?
MI'RA: trochu
Eagle: mělo by to nějaký efekt na výkon na dual-core procesorech?
Eagle: .. nebo to umíš jen tak, aby ti to usnadňovalo práci / někde si zaslechnul, že něco takového existuje ?
MI'RA: b je spravne
MI'RA: nicmene se da nejakym zpusobem kod rozlozit
MI'RA: viz MSDN
Eagle: jasně, takže neřešíš, protože ti to akorát komplikuje práci... jo?
MI'RA: yes
MI'RA: wokna si to vyresi samy
MI'RA: podle zatizeni
Eagle: no když netvoříš thready, tak je tvoje aplikace jednothreadová, tudíž nemůže mít žádný efekt z víceprocesorové mašiny
MI'RA: jasne
MI'RA: to jo
MI'RA: ale kdyz to napises ve vlaknech (coz neni potiz), tak uz si je system rozlozi podle uvazeni
Eagle: to jo, ale to je práce navíc a zdroj možných problémů
MI'RA: jj
Z uvedeného jasně vidíte, že napsat program s více thready se dá, ale je to komplikace navíc a možný zdroj problémů. Z rozhovoru výše je také zřejmé, že MI'RA thready nepoužívá, protože v tom pro sebe nevidí přínos. Nemůžeme ho obviňovat z lajdáctví, tohle je prostě realita. Zapomeňte na to, že jak se objeví dual-core procesory, budou pro ně programátoři optimalizovat. Nebudou! Nějakou snahu projeví pouze v situacích, kdy to bude nezbytně nutné.
Intel C++ Compiler - podpora OpenMP
OpenMP - to je to, co by mělo usnadnit v budoucnu multithreadové optimalizace prováděné programátorem. Jedná se o multiplatformní rozhranní, které umožňuje označovat části kódu poznámkami pro kompilátor. Ten ale musí tento standard podporovat - dnes běžně používané kompilátory toto nepodporují. Celý koncept OpenMP je v podstatě založen na tom, že programátor neřeší detaily nutné u threadů tvořených funkcemi Win32, ale jen označí blok "tohle proveď paralelně" a kompilátor tak učiní. OpenMP mu tak trochu usnadňuje programování... jenže stále za něj nevyřeší onen problém s otevřením šuplíku a neřeší za něj nutnost vyhledávat bloky kódu, které lze vykonávat paralelně. Proto i tento standard neřeší snadnost a finanční náročnost multithreadových optimalizací.
Kompilátorem vytvořené optimalizace
Některé nové kompilátory již podporují automatickou tvorbu threadů (většina bohužel ne), která má umožnit provedení optimalizací automaticky, bez zásahu programátora (tj. bez dalších nákladů). Zatím nejdál je asi Intel C++ Compiler. Ten v současné verzi nabízí auto-paralelelizátor.
Intel C++ Compiler - jednoduchý multithreading pro expanzi cyklů
Ten je však velmi jednoduchý. Hledá části kódu zvané cykly (kód, který se opakuje vícekrát, dokud není dosaženo nějaké podmínky), ze kterých vytvoří thready - cyklus je tak proveden paralelně. Ale to opět pouze za předpokladu, že výpočet něco takového umožňuje (že výpočty na sobě nejsou závislé). Bohužel taková optimalizace se dotýká pouze malé části kódu, ve většině případů navíc není možná či vytvoření threadů trvá déle než samotný výpočet. Jeden nezávislý programátor se k této optimalizaci vyjádřil v tom smyslu, že "Intelu umožňuje dosahovat výborných výsledků v benchmarku SPEC, ale pro naše použití je úplně k ničemu.".
Poznámka: Benchmark SPEC je průmyslově uznávaný test. Sestává ze SPECint měřícího výkonu v celočíselných operacích a SPECfp měřícího výkon v operacích s čísly s desetinnou čárkou.
Nová beta verze 9 kompilátoru pak umožňuje ještě optimalizace zvanou speculative precomputation (spekulativní předpočítávání), která zlepšuje výkon u jednovláknových aplikací, které třeba jinak ani nelze převést na thready, maskováním pomalostí pamětí RAM. Ani zde však není situace právě ideální. O detailech se zmíním za chvíli.