V poslední době se tady dost debatuje o různých aspektech různých jazyků, proto využiju tuto příležitost pro další téma. Na trhu se začínají objevovat 64 core / 128 thread CPU a nebude dlouho trvat a zcela běžně budou dostupné 256 core / thread CPU. Do pár let to máme na stole. A nastává otázka, jak pro tyto cpu programovat.
Tento článek je archiv z ABCLinuxu.cz, veškerou diskusi provozujme tam, děkuji.
Přístupů k multiprocesingu je celá řada. Nejjednodušší a nejtradičnější způsob je programovat malé singlethread programy, které lze snadno řetězit. Klasický UNIX přístup, na kterém není nic špatného. Pro použití těchto programů existuje celá řada „lepidel“ jako xargs
nebo parallel
pro spuštění jednoho programu nad hromadou souborů. Nebo použití pipe. Pomocí jednoduchých skriptů můžeme oba přístupy snadno kombinovat, pokud to charakter dat a jejich zpracování dovolí.
Co se týče podpory multiprocesing (nebo multithreading, budu to volně zaměňovat) přímo v programovacích jazycích, tady je situace horší. Ano, v klasickém C máme fork()
s tím, že si všechno (výměnu dat mezi procesy, zámky apod musíme pohlídat sami). Některé jazyky (Python) jsou vyloženě singlethread. Ne, že by nešlo programovat ve více vláknech, ale v základu všechno dost komplikuje GIL (global interpreter lock). Existují snadno použitelné knihovny, které to různými způsoby obcházejí (Multiprocessing). V Golangu máme gorutiny a kanály. V Rustu máme threads a message passing. Ale je tohle opravdu způsob, jak snadno napsat program pro 256 jader?
O co mi jde a tak trochu zopakuju jeden ne moc povedený dotaz, který jsem měl nedávno pod nějakým blogem. Proč neexistuje jazyk, který by multiprocesing řešit sám bez zásahu programátora? Když uvedu příklad z pythonu, tak v pythonu máme list comprehension, což je udělátko, které na vstupní seznam aplikuje nějakou fci a výstupem je opět seznam:
output = [f(item) for item in input]
Problém je, že jak celý vstupní, tak celý výstupní seznam se musí vejít do paměti. Tenhle problém se řeší pomocí generátorů a od toho máme generator comprehension:
output = (f(item) for item in input)
Pokud máte pocit, že to až na použité závorky vypadá úplně stejně, je to tak. Drtivou většinu list comprehension lze přepsat na generator comprehension a všechno bude fungovat. Výhodou generátorů je to, že jsou lazy, že další prvek vygenerují až je potřeba, na rozdíl od listu, který je kompletní. To znamená, že pomocí generátorů můžeme generovat nekonečné seznamy a taky můžeme jako prvky používat objemná data (já to takto používám pro obrázky, jejichž celkový objem je výrazně větší než dostupná paměť a přes to s nimi můžu pracovat jako s jedním seznamem).
Zpět k tématu. Python poskytuje snadno použitelnou knihovnu (Multiprocessing.Pool) pro zpracování dat ve více procesech. Použití triviální:
output = Pool().map(f, input)
Toto nám vyrobí seznam stejně jako list comprehension, ale využije k tomu automaticky všechny dostupné procesory. Což je moc fajn, pokud se nám vstupní a výstupní data vlezou do paměti. Je to rychlé, snadné na použití a efektivní.
Bohužel neexistuje nic jako Pool generátor. Tj že by pro výrobu další prvků používat více procesů, ale nevygeneroval by celý výstupní seznam naráz. Držel by si několik předzpracovaných prvků k okamžitému výdeji. Ano, toto si lze napsat pomocí jiných prostředků a mít tak frontu požadavků ke zpracování a ty zpracovávat paralelně (což ve skutečnosti mám, ale nepovažuju to za řešení ale spíš jako workaround). Co by se mi líbilo by byl automaticky paralelní generator comprehension.
Nebo ještě lépe, jazyk, který od programátora vůbec nevyžaduje hint ve stylu „tady bude další thread“ (tak jako třeba Golang i Rust vyžadují o C ani nemluvě). Prostě jazyk, který by sám dokázal generovat nezávislé úlohy a jejich závislosti, tak jak se třeba píšou pravidla pro Make (popis, jak vyrobit nějakou část dat a popis závislostí jednotlivých podúloh), dával si je do fronty a toto exekuoval na všech dostupných procesorech.
Je něco takového na obzoru? Vím, že existují speciální jazyky jako ERlang, ale v těch jsem nikdy nic „pořádného“ napsaného neviděl. Ano, používá se s výhodou tam, kde je potřeba řídit komunikaci many to many (tj ejabberd nebo rabbitmq) a ty nic nepočítají a erlang se tam používá spíš jen pro výměnu zpráv mezi jednotlivými interními thready (kterých mohou být snadno miliony) a nikoliv pro paralelní výpočet čehokoliv.
Tento článek je archiv z ABCLinuxu.cz, veškerou diskusi provozujme tam, děkuji.