Obsah
1. Programovací jazyk Julia: paralelní programování
2. Spuštění většího množství takzvaných „workerů“
3. Funkce workers(), nworkers() a nprocs()
4. Přidání lokálních i vzdálených workerů s využitím funkce addprocs()
6. Čekání na výsledek asynchronního výpočtu: wait() a fetch()
7. Demonstrační příklad: asynchronní výpočet s použitím funkcí remotecall() a fetch()
8. Minimalizace počtu posílaných zpráv: funkce remotecall_wait() a remotecall_fetch()
13. Obsah dalšího pokračování seriálu: úlohy a kanály
1. Programovací jazyk Julia: paralelní programování
V dnešní části seriálu o programovacím jazyce Julia se budeme zabývat další důležitou a taktéž užitečnou problematikou. Jedná se o práci s paralelními (synchronními i asynchronními) výpočty, které lze využít například v některých výpočtech z oblasti numerické matematiky, ve vybraných grafových algoritmech atd. Výpočty mohou běžet paralelně buď na jediném počítači vybaveném dnes již zcela běžným mikroprocesorem s větším množstvím jader, nebo lze vytvořit jednoduchý výpočetní cluster sestavený z běžných počítačů, které mezi sebou komunikují pomocí jednosměrných zpráv se spouštěnými funkcemi, jejich parametry či vypočtenými daty. Tím je zajištěno, že spouštěné výpočty budou mít přístup k lokálním datům; sdílení dat již vyžaduje použití sdílených polí či distribuovaných polí, což je problematika, které se budeme podrobněji věnovat později.
V programovacím jazyce Julia nalezneme hned celou řadu konceptů zjednodušujících práci s paralelně běžícími výpočty, ať již se jedná o základní nízkoúrovňové funkce typu remotecall, fetch, některé funkce vyššího řádu (pravděpodobně nejjednodušší je v této oblasti funkce pmap), o pomocná makra typu @spawn, @spawnat a @fetch, tak i o použití takzvaných kanálů (channels) či sdílených polí (shared arrays).
2. Spuštění většího množství takzvaných „workerů“
Prostředí programovacího jazyka Julia obsahuje již ve své základní instalaci základní podporu pro tvorbu výpočetních clusterů. Nejjednodušší je vytvoření takového (pseudo)clusteru na jediném počítači, který v dnešní době typicky obsahuje mikroprocesor se čtyřmi či dokonce osmi výpočetními jádry. Při spouštění interpretru příkazem julia je možné s využitím přepínače -p specifikovat množství takzvaných „workerů“, což jsou, poněkud zjednodušeně řečeno, samostatně běžící procesy přijímající zprávy posílané z interaktivní smyčky REPL. Obecně platí, že množství workerů by mělo odpovídat počtu výpočetních jader, ovšem konkrétní optimální konfigurace může být v některých případech odlišná (k tomu slouží podpora pro profilování, kterou si taktéž popíšeme). Zkusme si například spuštění interpretru a současně i deseti workerů:
julia -p 10
Na terminálu se po chvíli objeví výzva (prompt) interaktivní smyčky REPL interpretru:
_ _ _ _(_)_ | A fresh approach to technical computing (_) | (_) (_) | Documentation: http://docs.julialang.org _ _ _| |_ __ _ | Type "?help" for help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 0.4.5 (2016-03-18 00:58 UTC) _/ |\__'_|_|_|\__'_| | Official http://julialang.org/ release |__/ | x86_64-unknown-linux-gnu julia>
Současně se na pozadí spustí oněch deset požadovaných workerů, o čemž se můžeme velmi snadno přesvědčit:
ps ax |grep julia 2402 pts/3 Sl+ 0:04 ./julia -p 10 2406 ? Ssl 0:02 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2407 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2408 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2409 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2410 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2411 ? Ssl 0:02 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2412 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2413 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2414 ? Ssl 0:03 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker 2415 ? Ssl 0:02 /home/tester/temp/julia-2ac304dfba/bin/julia -Cx86-64 -J/home/tester/temp/julia-2ac304dfba/lib/julia/sys.so --bind-to 10.0.0.40 --worker
Alternativně lze použít příkaz pstree či top, samozřejmě pouze v případě, pokud jsou příslušné balíčky nainstalovány.
![](https://i.iinfo.cz/images/594/julia-10-workers-1-prev.png)
Obrázek 1: Procesy představující REPL a deset workerů systému Julia (zobrazeno nástrojem top).
3. Funkce workers(), nworkers() a nprocs()
Každému workeru je přiřazeno celé číslo, které je větší než jedna. Výjimkou je pouze stav, kdy je celé prostředí jazyka Julia představováno jediným procesem – v tomto případě má jediný worker přiřazeno číslo jedna, které je totožné s vlastní smyčkou REPL (tento proces se taktéž v kontextu většího clusteru nazývá master). Jednoznačné celočíselné identifikátory workerů vrací funkce workers(), kterou si ihned můžeme ve smyčce REPL otestovat:
help?> workers Base.workers() Returns a list of all worker process identifiers. julia> workers() 10-element Array{Int64,1}: 2 3 4 5 6 7 8 9 10 11
Počet dostupných workerů získáme snadno funkcí nworkers():
help?> nworkers Base.nworkers() Get the number of available worker processes. This is one less than nprocs(). Equal to nprocs() if nprocs() == 1. julia> nworkers() 10
Celkový počet všech procesů patřících do clusteru vrátí funkce nprocs(). Povšimněte si, že počet procesů je o vždy jedničku větší, než počet workerů, protože se přidal ještě proces s REPL (master):
help? nprocs Base.nprocs() Get the number of available processes. julia> nprocs() 11
4. Přidání lokálních i vzdálených workerů s využitím funkce addprocs()
Další workery lze již z běžícího interpretru (či z uživatelského skriptu) přidat funkcí nazvanou addprocs(). Pokud se této funkci nepředají další parametry, vytvoří se takový počet workerů, který odpovídá hodnotě CPU_CORES. Alternativně lze této funkci předat počet nových workerů:
Nejprve zjistíme aktuální hodnotu CPU_CORES:
julia> CPU_CORES 4
Následně k deseti již vytvořeným workerům přidáme další čtyři (na vašem počítači se však samozřejmě může jednat o odlišnou hodnotu v závislosti na počtu jader CPU):
julia> addprocs() 4-element Array{Int64,1}: 12 13 14 15
Další volání stejné funkce přidá další čtyři workery, takže jich je již dohromady osmnáct (10+4+4):
julia> addprocs() 4-element Array{Int64,1}: 16 17 18 19
Pro jistotu se podívejme, zda je to skutečně pravda:
julia> workers() 18-element Array{Int64,1}: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
S využitím stejné funkce addprocs() lze vytvořit i výpočetní cluster, v němž jsou procesy představující jednotlivé workery spuštěny na vzdáleném stroji či na vzdálených strojích. V nejjednodušším případě se funkci addprocs() předá pole se jmény (hostname) či IP adresami vzdálených počítačů. Pro spuštění se použije ssh:
julia> addprocs(["localhost"]) 1-element Array{Int64,1}: 21
Použijte řetězce i v případě, že se namísto jména použije IP adresa, protože 10.20.30.40 pochopitelně není korektně zapsané číslo:
julia> addprocs(["localhost", "cluster-machine-1", "10.0.0.40"]) 22 23 24
Kromě jména vzdáleného počítače či jeho IP adresy lze zadat i počet spouštěných workerů. V tomto případě musí pole obsahovat dvojici jméno:počet. Podívejme se na chování u nově spuštěné smyčky REPL, ke které je zpočátku navázán pouze jediný worker s identifikačním číslem jedna:
julia> workers() 1-element Array{Int64,1}: 1 julia> addprocs([("localhost", 10)]) 10-element Array{Int64,1}: 2 3 4 5 6 7 8 9 10 11 julia> workers() 10-element Array{Int64,1}: 2 3 4 5 6 7 8 9 10 11
Podrobnější podoba řetězce specifikujícího vzdálený počítač vypadá takto:
[user@]host[:port] [bind_addr[:port]]
V případě, že není zadáno jméno uživatele, použije se jméno vlastníka procesu smyčky REPL (tj. jméno uživatele, který spustil interaktivní prostředí). Port taktéž není zapotřebí zadat v případě, že se má použít standardní port 22. Položky bind_addr a popř. i číslo druhého portu použijte tehdy, pokud spolu mají jednotlivé workery komunikovat. Přidání dalších deseti workerů spuštěných pro uživatele „tester“ na počítači se jménem „cluster-2“ přes (nestandardní) SSH port 2222 tedy bude vypadat následovně:
julia> addprocs([("tester@cluster-2:2222", 10)]) 10-element Array{Int64,1}: 12 13 14 15 16 17 18 19 20 21
5. Funkce remotecall()
Základní funkcí určenou pro spuštění nějakého kódu v jiném procesu (samozřejmě stále v rámci vytvořeného clusteru) je funkce nazvaná jednoduše a přímočaře remotecall(). Jedná se o poměrně nízkoúrovňovou funkci, které je zapotřebí předat jak identifikátor procesu (celé číslo vracené funkcí workers()), tak i spouštěnou funkci a její parametry. Podívejme se nyní na příklad použití funkce remotecall(). Nejprve spustíme novou smyčku REPL a současně s ní i deset lokálně pracujících workerů:
$ ./julia -p 10 _ _ _ _(_)_ | A fresh approach to technical computing (_) | (_) (_) | Documentation: http://docs.julialang.org _ _ _| |_ __ _ | Type "?help" for help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 0.4.5 (2016-03-18 00:58 UTC) _/ |\__'_|_|_|\__'_| | Official http://julialang.org/ release |__/ | x86_64-unknown-linux-gnu
Pro jistotu si uvěříme, že počet spuštěných workerů skutečně odpovídá předanému parametru:
julia> nworkers() 10
Nyní si vzdáleně spustíme funkci ones(), kterou již známe z předchozích částí tohoto seriálu. Připomeňme si, že tato funkce vytvoří vektor či matici o zadané velikosti a naplní všechny prvky matice jedničkami. Lokální matice o velikosti 5×5 prvků se vytvoří takto:
julia> ones(Int8, 5, 5) 5x5 Array{Int8,2}: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Vzdálené spuštění funkce ones() na workeru s identifikátorem 2 bude vypadat následovně:
julia> ar2=remotecall(2, ones, Int8, 5, 5) RemoteRef{Channel{Any}}(2,1,16)
Dtto na workeru číslo 3:
julia> ar3=remotecall(3, ones, Int8, 5, 5) RemoteRef{Channel{Any}}(3,1,17)
Povšimněte si především způsobu předání parametrů vzdáleně volané funkci. Namísto:
ones(Int8, 5, 5)
je nutné zadat:
remotecall(3, ones, Int8, 5, 5)
Pokud si myslíte, že je tento zápis nečitelný, lze namísto něj použít makro @spawnat, s nímž se seznámíme v deváté kapitole.
Dále si povšimněte, že se nevrací přímo vytvořená matice, ale pouze objekt představující (velmi zjednodušeně řečeno) referenci na kanál, ze kterého se po dokončení výpočtu může vypočtená matice získat. Toto řešení bylo zvoleno z toho důvodu, aby se nemusely mezi jednotlivými workery přenášet obrovská množství dat.
Pozor je zapotřebí dát na to, že některé příkazy, typicky příkazy pro práci se standardním vstupem a výstupem, nebudou spolupracovat přímo s REPL, ale budou používat vlastní I/O operace:
julia> remotecall(2, print, 42) RemoteRef{Channel{Any}}(2,1,14)
Zde se nic nevypíše, a to ani při čekání na dokončení výpočtu.
6. Čekání na výsledek asynchronního výpočtu: wait() a fetch()
Výpočet spuštěný v některém workeru je samozřejmě prováděn paralelně a zcela nezávisle na smyčce REPL (samotná funkce remotecall je ukončena v tom okamžiku, kdy předá práci workerům). Ve chvíli, kdy potřebujeme získat výsledky takového výpočtu, je nutné nejprve zajistit, aby byl výpočet skutečně dokončen. To většinou nebude problém pro funkci ones(), která je interně velmi jednoduchá, ale pro složitější výpočty je mnohdy nutné počkat, až se tyto výpočty dokončí. K tomuto účelu slouží funkce nazvaná wait() (pouze čekání na dokončení výpočtu) a taktéž mnohem užitečnější funkce nazvaná fetch(), která nejenže počká na dokončení výpočtu, ale dokáže vrátit i jeho výsledky (což znamená, že se data přenesou z jednoho workeru do jiného workeru). Těmto funkcím lze předat referenci vrácenou již popsanou funkcí remotecall().
V předchozí kapitole jsme spustili dvě funkce, které na workerech číslo 2 a 3 vytvořily matice. Reference k těmto maticím jsou uloženy v proměnných ar2 a ar3. Získání obsahů matic je tedy velmi jednoduché:
julia> fetch(ar2) 5x5 Array{Int8,2}: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
julia> fetch(ar3) 5x5 Array{Int8,2}: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Poznámka: funkce fetch je natolik inteligentní, že když ji použijete na lokální data (= data dostupná aktivnímu workeru), nebude provádět žádné pomalé přenosy mezi procesy, ale přímo tato data vrátí. To je výhodné, protože u mnoha algoritmů nemusí být jasné, ve kterém okamžiku pracují s lokálními daty a ve kterém okamžiku s daty uloženými na jiném workeru (a potenciálně na jiném počítači).
7. Demonstrační příklad: asynchronní výpočet s použitím funkcí remotecall() a fetch()
Připomeňme si, že i běžné operátory pro součet, rozdíl atd. je ve skutečnosti možné použít dvěma způsoby – buď ve funkci klasického infixového binárního operátoru zapisovaného mezi oba operandy, nebo jako funkci. To je užitečné, neboť nám to umožňuje provést například součet prvků matic ve vybraném workeru, což je ukázáno na dalším příkladu:
julia> ar2_10=remotecall(2, +, fetch(ar2), 10) RemoteRef{Channel{Any}}(2,1,29)
julia> fetch(ar2_10) 5x5 Array{Int8,2}: 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11
Otázka pro čtenáře: prvním parametrem funkce/operátoru + je fetch(ar2). Znamená to, že se celá matice přenese do smyčky REPL (aby se reference vyhodnotila na matici), nebo se veškerý výpočet provede na workeru číslo 2 bez přenosu dat?
Pozor je zapotřebí dát na to, že funkce definované v jednom workeru (či v REPLu) nejsou ihned viditelné v ostatních workerech:
julia> function add(x,y) return x+y end add (generic function with 1 method) julia> add(1,2) 3 julia> fetch(remotecall(2, add, 1, 2)) ERROR: On worker 2: function add not defined on process 2 in error at ./error.jl:21 in anonymous at serialize.jl:526 in anonymous at multi.jl:920 in run_work_thunk at multi.jl:661 in run_work_thunk at multi.jl:670 in anonymous at task.jl:58 in remotecall_fetch at multi.jl:747 in call_on_owner at multi.jl:793 in fetch at multi.jl:811
Tento problém se řeší uložením funkcí do modulů a načtením těchto modulů do všech workerů.
8. Minimalizace posílaných zpráv: funkce remotecall_wait() a remotecall_fetch()
V předchozích příkladech jsme se setkali s poměrně typickým problémem – potřebujeme v nějakém workeru spustit zvolenou funkci a po dokončení výpočtu vrátit její výsledek. Tato logika je implementována (opět na relativně nízké úrovni) ve funkcích nazvaných remotecall_wait a remotecall_fetch. Jak již názvy těchto funkcí napovídají, jedná se o kombinaci volání remotecall+wait popř. remotecall+fetch, a to bez nutnosti pracovat s mezivýsledkem (referencí na „vzdálená“ data). Předchozí příklady by tedy bylo možné zapsat i následujícím způsobem:
julia> a=remotecall_fetch(2, ones, 5, 5) 5x5 Array{Float64,2}: 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 julia> b=remotecall_fetch(2, +, a, 10) 5x5 Array{Float64,2}: 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0
Opět platí, že nelze volat funkci, která není načtena (a přeložena) v příslušnému workeru:
julia> function add(x,y) return x+y end add (generic function with 1 method) julia> add(1,2) 3 julia> remotecall_wait(2, add, 1, 2) ERROR: On worker 2: function add not defined on process 2 in error at ./error.jl:21 in anonymous at serialize.jl:526 in anonymous at multi.jl:930 in run_work_thunk at multi.jl:661 in run_work_thunk at multi.jl:670 in anonymous at task.jl:58 in remotecall_wait at multi.jl:764 in remotecall_wait at multi.jl:768 julia> remotecall_fetch(2, add, 1, 2) ERROR: On worker 2: function add not defined on process 2 in error at ./error.jl:21 in anonymous at serialize.jl:526 in anonymous at multi.jl:923 in run_work_thunk at multi.jl:661 [inlined code] from multi.jl:923 in anonymous at task.jl:63 in remotecall_fetch at multi.jl:747 in remotecall_fetch at multi.jl:750
K čemu jsou tyto funkce vlastně vhodné? Vždyť pouze vzdáleně spouštíme výpočet, na jehož výsledek čekáme, takže smyčka REPL v tomto čase stejně nereaguje na příkazy uživatele. Použití je hned několik – spouštění funkcí na mnohem výkonnějším stroji, než je (například) notebook se spuštěnou smyčkou REPL či použití těchto funkcí ve skriptu, v němž se používá větší množství vláken.
9. Pomocné makro @spawnat
Přímé použití funkce remotecall popř. některé z jejích variant pojmenovaných remotecall_wait a remotecall_fetch je v mnoha ohledech poněkud nešikovné, protože jak volaná vzdálená funkce, tak i její parametry se zapisují jiným způsobem než jak je tomu při volání lokální funkce. Tento problém do značné míry řeší makro nazvané @spawnat, kterému se předá identifikátor workeru (celé kladné číslo) a dále výraz, který je patřičně transformován a následně je interně zavolána funkce remotecall.
help?> @spawnat @spawnat Accepts two arguments, p and an expression. A closure is created around the expression and run asynchronously on process p. Returns a RemoteRef to the result.
Celý zápis je tak mnohem čitelnější, ostatně se můžeme podívat na příklad:
julia> s=@spawnat 2 ones(5, 5) RemoteRef(2,1,19) julia> fetch(s) 5x5 Array{Float64,2}: 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
Povšimněte si, že mezi identifikátorem workeru a zápisem volání (vzdálené) funkce nefiguruje žádný oddělovač kromě mezery.
Použít lze i složitější zápisy:
julia> s=@spawnat 2 (ones(5,5)+10)*20 RemoteRef{Channel{Any}}(2,1,13) julia> fetch(s) 5x5 Array{Float64,2}: 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0
10. Pomocné makro @spawn
V mnoha situacích nás nemusí zajímat, ve kterém workeru se nějaký výpočet provede. V tomto případě je možné namísto makra @spawnat použít nepatrně jednodušší makro nazvané pouze @spawn:
help?> @spawn @spawn Creates a closure around an expression and runs it on an automatically-chosen process, returning a RemoteRef to the result.
Toto makro automaticky vybere nějaký vhodný worker a spustí v něm zadanou funkci či výraz převedený na volání funkce. Díky tomu není nutné (a ve skutečnosti dokonce ani možné) makru předat identifikátor workeru:
julia> s=@spawn (ones(5,5)+10)*20 RemoteRef{Channel{Any}}(3,1,17) julia> fetch(s) 5x5 Array{Float64,2}: 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0 220.0
Nepatrně složitější příklad:
julia> ar2=remotecall(2, ones, Int8, 5, 5) RemoteRef{Channel{Any}}(2,1,16) julia> ar2_20 = @spawn fetch(ar2) + 20 RemoteRef{Channel{Any}}(3,1,36) julia> fetch(ar2_20) 5x5 Array{Int8,2}: 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21
11. Pomocné makro @fetch
Podobně jako existuje kombinace funkcí remotecall a fetch implementovaná ve funkci nazvané remotecall_fetch, i k makru @spawn popsaného v předchozí kapitole existuje alternativa pojmenovaná @fetch. Jedná se vlastně o kombinaci spuštění vybrané funkce na libovolném workeru s čekáním na výsledek a vrácením tohoto výsledku:
help?> @fetch @fetch Equivalent to fetch(@spawn expr).
Ukázky použití tohoto makra jsou jednoduché:
julia> s=@fetch ones(5, 5) 5x5 Array{Float64,2}: 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
julia> r=@fetch +(s,10) 5x5 Array{Float64,2}: 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0 11.0
julia> t=@fetch s*10 5x5 Array{Float64,2}: 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0
Poznámka: jednotlivé kroky mohou být ve skutečnosti provedeny v rozdílných workerech.
12. Makro @everywhere
Poněkud speciální význam má makro nazvané @everywhere:
help?> @everywhere @everywhere Execute an expression on all processes. Errors on any of the processes are collected into a CompositeException and thrown.
Jak již název tohoto makra napovídá, slouží ke spuštění zvolené funkce ve všech workerech (i v REPLu), ovšem přitom se nevrací žádné výsledky:
julia> @everywhere print(42) 42
To v některých případech nemusí vadit, protože například příkaz pro načtení či znovunačtení vybraného modulu je vhodné spouštět na všech workerech a výsledek této operace není příliš zajímavý. Případné výjimky se však zpracují korektně:
julia> @everywhere throw("Exception") ERROR: On worker 2: "Exception" in eval at ./sysimg.jl:14 in anonymous at multi.jl:1394 in anonymous at multi.jl:923 in run_work_thunk at multi.jl:661 [inlined code] from multi.jl:923 in anonymous at task.jl:63 in remotecall_fetch at multi.jl:747 in remotecall_fetch at multi.jl:750 in anonymous at multi.jl:1396 ...and 10 other exceptions. in sync_end at ./task.jl:413 in anonymous at multi.jl:1405
13. Obsah dalšího pokračování seriálu: úlohy a kanály
V následující části seriálu o programovacím jazyku Julia dokončíme téma, kterému jsme se začali věnovat dnes. Popíšeme si totiž koncept takzvaných kanálů (channels) a úloh (tasks). S využitím úloh je možné implementovat jak klasické koprogramy (používané například v programovacím jazyku Lua) nebo dokonce plnohodnotná vlákna, což znamená, že se paralelní výpočty nemusí omezovat pouze na použití většího množství procesů a komunikaci mezi těmito procesy. Kanály se používají pro kooperaci mezi různými částmi výpočtu a lze s nimi v případě potřeby realizovat i různé synchronizační mechanismy. S principem funkce komunikačních kanálů jsme se ostatně seznámili i v paralelně (a nyní již poněkud nepravidelně) běžícím seriálu o programovacím jazyku Clojure, konkrétně se jednalo o část nazvanou Asynchronní programování v Clojure s využitím knihovny core.async .
14. Odkazy na Internetu
- CS 2101 Parallel Computing with Julia
https://www.coursehero.com/file/11508091/CS-2101-Parallel-Computing-with-Julia/ - Julia By Example
https://samuelcolvin.github.io/JuliaByExample/ - Tasks and Parallel Computing
http://docs.julialang.org/en/release-0.4/stdlib/parallel/ - clock(3) – Linux man page
http://linux.die.net/man/3/clock - rand_r(3) – Linux man page
http://linux.die.net/man/3/rand_r - atan2(3) – Linux man page
http://linux.die.net/man/3/atan2 - Calling C and Fortran Code
http://docs.julialang.org/en/release-0.4/manual/calling-c-and-fortran-code/?highlight=symbol - Array Programming
https://en.wikipedia.org/wiki/Array_programming - Discovering Array Languages
http://archive.vector.org.uk/art10008110 - no stinking loops – Kalothi
http://www.nsl.com/ - Vector (obsahuje odkazy na články, knihy a blogy o programovacích jazycích APL, J a K)
http://www.vector.org.uk/ - APL Interpreters
http://www.vector.org.uk/?area=interpreters - APL_(programming_language
http://en.wikipedia.org/wiki/APL_(programming_language - APL FAQ
http://www.faqs.org/faqs/apl-faq/ - APL FAQ (nejnovější verze)
http://home.earthlink.net/~swsirlin/apl.faq.html - A+
http://www.aplusdev.org/ - APLX
http://www.microapl.co.uk/ - FreeAPL
http://www.pyr.fi/apl/index.htm - J: a modern, high-level, general-purpose, high-performance programming language
http://www.jsoftware.com/ - K, Kdb: an APL derivative for Solaris, Linux, Windows
http://www.kx.com - openAPL (GPL)
http://sourceforge.net/projects/openapl - Parrot APL (GPL)
http://www.parrotcode.org/ - Learning J (Roger Stokes)
http://www.jsoftware.com/help/learning/contents.htm - Rosetta Code
http://rosettacode.org/wiki/Main_Page - Why APL
http://www.acm.org/sigapl/whyapl.htm - Introducing Julia/Functions
https://en.wikibooks.org/wiki/Introducing_Julia/Functions - Functions (Julia documentation)
http://docs.julialang.org/en/release-0.4/manual/functions/ - Evaluate binomial coefficients
http://rosettacode.org/wiki/Evaluate_binomial_coefficients - Ackermann function
http://rosettacode.org/wiki/Ackermann_function - Julia (front page)
http://julialang.org/ - Julia – dokumentace
http://docs.julialang.org/en/release-0.4/ - Julia – repositář na GitHubu
https://github.com/JuliaLang/julia - Julia (programming language)
https://en.wikipedia.org/wiki/Julia_%28programming_language%29 - IJulia
https://github.com/JuliaLang/IJulia.jl - Introducing Julia
https://en.wikibooks.org/wiki/Introducing_Julia - Julia: the REPL
https://en.wikibooks.org/wiki/Introducing_Julia/The_REPL - Introducing Julia/Metaprogramming
https://en.wikibooks.org/wiki/Introducing_Julia/Metaprogramming - Month of Julia
https://github.com/DataWookie/MonthOfJulia - Learn X in Y minutes (where X=Julia)
https://learnxinyminutes.com/docs/julia/ - New Julia language seeks to be the C for scientists
http://www.infoworld.com/article/2616709/application-development/new-julia-language-seeks-to-be-the-c-for-scientists.html - Julia: A Fast Dynamic Language for Technical Computing
http://karpinski.org/publications/2012/julia-a-fast-dynamic-language - The LLVM Compiler Infrastructure
http://llvm.org/ - Julia: benchmarks
http://julialang.org/benchmarks/ - Type system
https://en.wikipedia.org/wiki/Type_system - Half-precision floating-point format
https://en.wikipedia.org/wiki/Half-precision_floating-point_format