TakéParalelní výpočty, Paralelismus, Parallel computingPokročilý

Definice

Paralelní zpracování je způsob výpočtu, při kterém se úloha rozdělí na části běžící současně na více výpočetních jednotkách, typicky na jádrech procesoru, na GPU nebo na několika strojích v clusteru. Cílem je zkrátit dobu výpočtu, přičemž zrychlení omezuje ta část úlohy, kterou rozdělit nelze, a režie na komunikaci mezi částmi.

Kategorie: Počítačová architekturaAktualizováno

Než se na to spolehnete: Wikidata ID Q232661 pro Parallel computing uvádím z paměti, doporučuji ověřit.

Proč se úloha vůbec dělí

Taktovací frekvence procesorů narazila na tepelný strop, a tak výrobci místo rychlejších jader přidávají jádra další. Jednovláknový kód z toho nic nemá: běží pořád stejně rychle na jednom jádře, zatímco ostatní se nudí. Paralelní zpracování je odpověď na tenhle posun. Úloha se rozseká na části, které lze počítat nezávisle, každá dostane vlastní výpočetní jednotku a výsledky se na konci složí dohromady.

Kde je strop zrychlení

Zrychlení nikdy neroste lineárně s počtem jader. Amdahlův zákon říká, že celkový zisk limituje sériová část programu: pokud je 10 % kódu nerozdělitelných, ani nekonečně mnoho jader úlohu nezrychlí víc než desetkrát. K tomu se přidává režie: rozdělení dat, synchronizace, přenosy mezi pamětí CPU a GPU, slučování výsledků. U malých vstupů bývá tahle režie dražší než samotný výpočet a paralelní verze doběhne pomaleji než sériová.

Paralelismus versus konkurence

Konkurence (concurrency) znamená, že program zvládá více rozpracovaných úloh najednou a přepíná mezi nimi, i kdyby měl jediné jádro. Paralelismus znamená, že se výpočty skutečně dějí ve stejný okamžik na různém hardwaru. Webový server obsluhující tisíc pomalých požadavků potřebuje hlavně konkurenci, protože většinu času čeká na síť a disk. Renderování snímku nebo trénink modelu potřebuje paralelismus, protože jde o čistou práci procesoru.

Datový a úlohový paralelismus

Datový paralelismus aplikuje stejnou operaci na různé části dat: součet miliardy čísel, filtr obrázku, násobení matic. Tenhle typ je ideální pro GPU a pro vektorové instrukce, viz shader. Úlohový paralelismus naopak spouští odlišné operace současně: jedno vlákno stahuje data, druhé je parsuje, třetí zapisuje do databáze.

Sdílená paměť a rozdělená paměť

Ve sdílené paměti vidí všechna vlákna stejná data, což je rychlé, ale otevírá to dveře souběhům. Bez zámků, atomických operací nebo neměnných struktur vzniká race condition, kterou je těžké reprodukovat. V modelu s rozdělenou pamětí (procesy, cluster, MapReduce) si uzly nic nesdílejí a posílají si zprávy; ladí se lépe, platí se ale komunikací po síti.

Co to znamená pro běžnou aplikaci

Většina webových aplikací neparalelizuje ručně: práci rozděluje runtime, databáze nebo load balancer napříč instancemi. Ruční paralelizace se vyplatí u dávkového zpracování, obrazových a video operací, generování reportů nad velkými daty a u strojového učení. Prvním krokem by vždy mělo být měření: profil ukáže, jestli je úzkým hrdlem procesor (paralelismus pomůže), nebo čekání na I/O (pomůže spíš asynchronní model a lepší dotazy).

Příklady z praxe

  1. Zmenšování náhledů fotek na více jádrech

    E-shop nahrává dávku 2 000 fotek produktů a ke každé generuje tři velikosti náhledu. Sériově běží zpracování minuty, protože každá fotka je nezávislá na ostatních, jde o učebnicový datový paralelismus. Rozdělení práce mezi procesy podle počtu jader zkrátí dobu zhruba úměrně počtu jader, dokud nenarazí na propustnost disku.

    from concurrent.futures import ProcessPoolExecutor
    from PIL import Image
    
    def make_thumb(path):
        with Image.open(path) as img:
            img.thumbnail((400, 400))
            img.save(path.replace('.jpg', '_400.jpg'))
        return path
    
    if __name__ == '__main__':
        with ProcessPoolExecutor() as pool:
            list(pool.map(make_thumb, paths))
  2. Když paralelizace uškodí

    Vývojář přepíše součet pole o 5 000 prvcích na paralelní verzi se čtyřmi vlákny a naměří, že nová verze je pomalejší. Důvod je režie: vytvoření vláken, rozdělení dat a sečtení dílčích výsledků stojí víc než samotné sčítání. Paralelní varianta se začne vyplácet až u řádově větších vstupů, hranici je nutné najít měřením, ne odhadem.

Časté omyly

MýtusKdyž použiju dvakrát víc jader, program poběží dvakrát rychleji.
Ve skutečnostiZrychlení omezuje sériová část úlohy a režie na synchronizaci a přenos dat. Podle Amdahlova zákona i malý podíl nerozdělitelného kódu tvrdě zastropuje maximální zisk, takže reálné zrychlení bývá výrazně nižší než počet jader.
MýtusParalelní zpracování a asynchronní kód jsou totéž.
Ve skutečnostiAsynchronní kód řeší čekání na I/O a může běžet na jediném vlákně; skutečný souběžný výpočet se při něm dít nemusí. Paralelní zpracování znamená současný běh na více jádrech nebo strojích a pomáhá u úloh vytížených procesorem.
MýtusStačí přidat vlákna a hotovo.
Ve skutečnostiSdílený stav bez synchronizace vede k souběhům, deadlockům a chybám, které se objevují nepravidelně a hůř se reprodukují. Paralelní kód vyžaduje jasné rozdělení dat, neměnné struktury nebo explicitní zamykání.

Časté dotazy

Jak poznám, že se moje úloha na paralelní zpracování hodí?
Paralelní zpracování se vyplatí, když je úloha vytížená procesorem, dá se rozdělit na části s minimem sdíleného stavu a jednotlivé části trvají dostatečně dlouho, aby převážily režii na jejich rozdělení. Dobrými kandidáty jsou hromadné převody obrázků, generování reportů nad velkými datovými sadami, simulace nebo trénink modelů. Naopak úlohy, které většinu času čekají na databázi, disk nebo externí API, paralelizací jader nezrychlíte, tam pomůže asynchronní model a optimalizace dotazů. Rozhodnutí by mělo vzejít z profilování, ne z odhadu.
Kolik vláken nebo procesů mám nastavit?
Pro výpočetně náročné úlohy bývá rozumným výchozím bodem počet vláken odpovídající počtu logických jader stroje; víc vláken jen zvyšuje přepínání kontextu bez zisku. Pro úlohy čekající na I/O může být smysluplný počet výrazně vyšší, protože vlákna většinu času nic nepočítají. V kontejnerech je nutné ověřit, kolik jader proces skutečně dostane, protože limity CPU nemusí být viditelné přes běžné systémové API. Konečné číslo patří do konfigurace a mělo by být podložené měřením na cílovém prostředí.
Proč je paralelní kód tak náročný na ladění?
Paralelní kód se chová nedeterministicky: pořadí, ve kterém vlákna přistupují ke sdíleným datům, se mění mezi jednotlivými běhy. Chyba typu race condition se proto může projevit jednou za tisíc spuštění a při ladění zmizí, protože debugger změní časování. Pomáhá minimalizovat sdílený stav, používat neměnné datové struktury, předávat data zprávami místo sdílení paměti a nasadit nástroje pro detekci souběhů a sanitizéry vláken. Stresové testy s vysokým počtem opakování odhalí víc než jednorázový průchod.
Jaký je vztah paralelního zpracování a GPU?
GPU je hardware navržený pro extrémní datový paralelismus: obsahuje tisíce jednoduchých jader, která zároveň provádějí stejnou operaci nad různými prvky dat. Úlohy jako násobení matic, filtrace obrazu nebo trénink neuronových sítí proto na GPU běží řádově rychleji než na procesoru. Podmínkou je, že algoritmus má pravidelnou strukturu a málo větvení; kód plný podmínek a nepravidelných přístupů do paměti výhodu GPU ztrácí. Počítat je nutné také s cenou přenosu dat mezi hlavní pamětí a pamětí grafické karty.

Zdroje

  1. Parallel computing(otevře se v novém okně)Wikipedia
  2. concurrent.futures: Launching parallel tasks(otevře se v novém okně)Python Software Foundation
  3. Amdahl's law(otevře se v novém okně)Wikipedia
  4. Worker threads(otevře se v novém okně)OpenJS Foundation

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.