Hlavní navigace

Programovací jazyk Julia: paralelní programování

12. 7. 2016
Doba čtení: 18 minut

Sdílet

Dnes se budeme věnovat práci s paralelními výpočty, neboť jazyk Julia nabízí vývojářům hned několik způsobů využití mikroprocesorů s větším množstvím jader popř. dokonce výpočetních clusterů.

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()

5. Funkce remotecall()

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()

9. Pomocné makro @spawnat

10. Pomocné makro @spawn

11. Pomocné makro @fetch

12. Makro @everywhere

13. Obsah dalšího pokračování seriálu: úlohy a kanály

14. Odkazy na Internetu

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.

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:

CS24_early

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

  1. CS 2101 Parallel Computing with Julia
    https://www.coursehero.com/fi­le/11508091/CS-2101-Parallel-Computing-with-Julia/
  2. Julia By Example
    https://samuelcolvin.github­.io/JuliaByExample/
  3. Tasks and Parallel Computing
    http://docs.julialang.org/en/release-0.4/stdlib/parallel/
  4. clock(3) – Linux man page
    http://linux.die.net/man/3/clock
  5. rand_r(3) – Linux man page
    http://linux.die.net/man/3/rand_r
  6. atan2(3) – Linux man page
    http://linux.die.net/man/3/atan2
  7. Calling C and Fortran Code
    http://docs.julialang.org/en/release-0.4/manual/calling-c-and-fortran-code/?highlight=symbol
  8. Array Programming
    https://en.wikipedia.org/wi­ki/Array_programming
  9. Discovering Array Languages
    http://archive.vector.org­.uk/art10008110
  10. no stinking loops – Kalothi
    http://www.nsl.com/
  11. Vector (obsahuje odkazy na články, knihy a blogy o programovacích jazycích APL, J a K)
    http://www.vector.org.uk/
  12. APL Interpreters
    http://www.vector.org.uk/?a­rea=interpreters
  13. APL_(programming_language
    http://en.wikipedia.org/wi­ki/APL_(programming_langu­age
  14. APL FAQ
    http://www.faqs.org/faqs/apl-faq/
  15. APL FAQ (nejnovější verze)
    http://home.earthlink.net/~swsir­lin/apl.faq.html
  16. A+
    http://www.aplusdev.org/
  17. APLX
    http://www.microapl.co.uk/
  18. FreeAPL
    http://www.pyr.fi/apl/index.htm
  19. J: a modern, high-level, general-purpose, high-performance programming language
    http://www.jsoftware.com/
  20. K, Kdb: an APL derivative for Solaris, Linux, Windows
    http://www.kx.com
  21. openAPL (GPL)
    http://sourceforge.net/pro­jects/openapl
  22. Parrot APL (GPL)
    http://www.parrotcode.org/
  23. Learning J (Roger Stokes)
    http://www.jsoftware.com/hel­p/learning/contents.htm
  24. Rosetta Code
    http://rosettacode.org/wiki/Main_Page
  25. Why APL
    http://www.acm.org/sigapl/whyapl.htm
  26. Introducing Julia/Functions
    https://en.wikibooks.org/wi­ki/Introducing_Julia/Functi­ons
  27. Functions (Julia documentation)
    http://docs.julialang.org/en/release-0.4/manual/functions/
  28. Evaluate binomial coefficients
    http://rosettacode.org/wi­ki/Evaluate_binomial_coef­ficients
  29. Ackermann function
    http://rosettacode.org/wi­ki/Ackermann_function
  30. Julia (front page)
    http://julialang.org/
  31. Julia – dokumentace
    http://docs.julialang.org/en/release-0.4/
  32. Julia – repositář na GitHubu
    https://github.com/JuliaLang/julia
  33. Julia (programming language)
    https://en.wikipedia.org/wi­ki/Julia_%28programming_lan­guage%29
  34. IJulia
    https://github.com/JuliaLan­g/IJulia.jl
  35. Introducing Julia
    https://en.wikibooks.org/wi­ki/Introducing_Julia
  36. Julia: the REPL
    https://en.wikibooks.org/wi­ki/Introducing_Julia/The_REPL
  37. Introducing Julia/Metaprogramming
    https://en.wikibooks.org/wi­ki/Introducing_Julia/Meta­programming
  38. Month of Julia
    https://github.com/DataWo­okie/MonthOfJulia
  39. Learn X in Y minutes (where X=Julia)
    https://learnxinyminutes.com/doc­s/julia/
  40. New Julia language seeks to be the C for scientists
    http://www.infoworld.com/ar­ticle/2616709/application-development/new-julia-language-seeks-to-be-the-c-for-scientists.html
  41. Julia: A Fast Dynamic Language for Technical Computing
    http://karpinski.org/publi­cations/2012/julia-a-fast-dynamic-language
  42. The LLVM Compiler Infrastructure
    http://llvm.org/
  43. Julia: benchmarks
    http://julialang.org/benchmarks/
  44. Type system
    https://en.wikipedia.org/wi­ki/Type_system
  45. Half-precision floating-point format
    https://en.wikipedia.org/wiki/Half-precision_floating-point_format

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.