Párhuzamos kiértékelés

Párhuzamos kiértékelés

Haskell függvény:

-- f x = x * x + sin (x + 2)

A programozó segíti a fordítóprogramot a kiértékelési startégia (részleges) megadásával.

Kiértékelési stratégia

-- import Control.Parallel   -- parallel csomagban

A stratégia megadása két részből áll, ehhez két primitív művelet:

  1. Mit értékeljünk ki párhuzamosan
par :: a -> b -> b
  1. Kiértékelési sorrend szabályozása
pseq :: a -> b -> b

Ezek a parallel csomag Control.Parallel moduljában találhatók.

par definíciója

Típus:

par :: a -> b -> b

Funkcionális definíció:

-- a `par` b = b 

Mielőtt visszaadja b-t, a kiértékelését párhuzamosan elindítja.

Hatásmechanizmus: szikra (spark) –> GHC szál –> OS szál


par a b és b minden programban szabadon felcserélhető anélkül, hogy a program jelentése megváltozna. De a program bizonyos viselkedése (mennyi memóriát és processzoridőt használ) megváltozhat. Ezeket nem funkcionális tulajdonságoknak nevezzük.

Minden par híváskor egy szikra keletkezik. A futásidejű rendszer eldönti, hogy indítson-e egy GHC szálat (ez egy gyors döntés). Minden GHC szál egy operációs rendszer szálra képződik le. Tipikusan annyi operációs rendszer szál van, ahány processzor / processzor mag, ettől sokkal több a GHC szálak száma. A GHC szálak nagyon könnyűek (kevés erőforrás kell a menedzselésükhöz).

par használata

Példa:

x = 2^10 `par` 1024 + 3^10

2^10 párhuzamosan értékelődik ki de az eredmény elvész.

Hogy lehet felhasználni az 2^10 végeredményét, mondjuk a 1024 helyett?

par használata (folytatás)

Egy megoldás:

-- x = t `par` t + 3^10  where t = 2^10

Probléma:

Elveszett a lényeg!


A következő megoldás jó, de látszik hogy nagyon törékeny a kód az átalakításokkal szemben:

pseq definíciója

Típus:

pseq :: a -> b -> b

Funkcionális definíció:

-- a `pseq` b = b 

Mielőtt visszaadja b-t kiértékeli a-t.

pseq használata

Jó megoldás:

-- x = t `par` s `pseq` t + s  where 

--         t = 2^10
--         s = 3^10

Kísérlet

-- import Control.Parallel
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
fib' n = a `par` b `pseq` a + b  where
    a = fib (n-1)
    b = fib (n-2)
*Main> :s +s
*Main> fib 28
*Main> fib' 28
bash$ ghc -O2 --make -threaded X.hs
bash$ ./X +RTS -N4

Feladatok

  1. Definiáljunk egy párhuzamos összeadó műveletet!
a |+| b = a `par` b `pseq` a + b
  1. Készítsük el az előző fib azon variánsát, ami minden összeadást párhuzamosan végez!
fibFull 0 = 0
fibFull 1 = 1
fibFull n = fibFull (n-1) |+| fibFull (n-2)
  1. Készítsünk egy olyan fib variánst ami párhuzamosan végzi el az összeadást, feltéve hogy a bemenete nagyobb, mint 20! Nézzük meg gyorsult-e a definíció!

Magasabb szintű stratégiák

Csupán par és pseq használatával mindenféle többszálú kiértékelés megoldható, de vannak előre elkészített stratégiák a Control.Parallel.Strategies modulban:

parMap :: Strategy b -> (a -> b) -> [a] -> [b]

Stratégia kombinátorok

-- type Strategy a = a -> Eval a
-- data Eval a = Seq a | Par a | Lazy a
withStrategy :: Strategy a -> a -> a
rpar :: Strategy a -- párhuzamosan
rseq :: Strategy a -- felszínesen
rdeepseq :: NFData a => Strategy a -- mélyen
parList :: Strategy a -> Strategy [a]
evalList :: Strategy a -> Strategy [a]
parMap :: Strategy b -> (a -> b) -> [a] -> [b]
parMap strat f = withStrategy (parList strat) . map f 

Összefoglalás