2012-09-01 15 views
9

Mam wdrożenie gry życia Conwaya. Chcę go przyspieszyć, jeśli to możliwe, używając paralelizmu.Haskell parMap i paralelizm

life :: [(Int, Int)] -> [(Int, Int)] 
life cells = map snd . filter rules . freq $ concatMap neighbours cells 
    where rules (n, c) = n == 3 || (n == 2 && c `elem` cells) 
      freq = map (length &&& head) . group . sort 

parLife :: [(Int, Int)] -> [(Int, Int)] 
parLife cells = parMap rseq snd . filter rules . freq . concat $ parMap rseq neighbours cells 
    where rules (n, c) = n == 3 || (n == 2 && c `elem` cells) 
      freq = map (length &&& head) . group . sort 

neigbours :: (Int, Int) -> [(Int, Int)] 
neighbours (x, y) = [(x + dx, y + dy) | dx <- [-1..1], dy <- [-1..1], dx /= 0 || dy /= 0] 

w profilowaniu, sąsiedzi stanowi 6,3% czasu spędzonego, więc podczas gdy małe spodziewałem się zauważalne przyspieszenie przez odwzorowanie go równolegle.

I badane w funkcji prostego

main = print $ last $ take 200 $ iterate life fPent 
    where fPent = [(1, 2), (2, 2), (2, 1), (2, 3), (3, 3)] 

i zebrane wersję równoległym

ghc --make -O2 -threaded life.hs 

i uruchomiono go jako

./life +RTS -N3 

okazuje się, że wersja równoległego jest mniejsza . Czy używam tutaj niepoprawnej mapy? czy jest to nawet przypadek, w którym można zastosować paralelizm?

+0

Po pierwsze, masz co najmniej 3 rdzenie w swoim komputerze? Po drugie, równoległość zawsze wiąże się z pewnym obciążeniem, więc jeśli praca wykonywana przez każdy wątek jest bardzo mała, dodatkowe obciążenie będzie większe niż jakiekolwiek przyspieszenia. – huon

+0

Mam i5-2500k, więc jest zdecydowanie do 4 rdzeni dostępnych – cdk

+0

Zauważ, że możesz uzyskać znacznie większe przyspieszenia od ulepszenia algorytmu niż zrównoleglania. Większość czasu spędza się w 'sort' i' elem'. Wykorzystując fakt, że lista komórek jest sortowana (i zmienia się 'fPent', aby była sortowana), można z grubsza zmniejszyć o połowę czas. –

Odpowiedz

2

Nie sądzę, że mierzysz w prawo. Twój parLife jest rzeczywiście nieco szybszy niż life. W rzeczywistości na mojej maszynie (Phenom X4, 4 rdzenie) pierwsza zajmuje tylko 92,5% czasu, który robi, co biorąc pod uwagę, że mówisz, że spodziewasz się tylko 6% poprawy, jest całkiem dobra.

Jaka jest konfiguracja testu porównawczego? Czy próbowałeś używać criterion? Oto co zrobiłem:

import Criterion 
import Criterion.Main 

-- your code, minus main 

runGame f n = last $ take n $ iterate f fPent 
    where fPent = [(1, 2), (2, 2), (2, 1), (2, 3), (3, 3)] 

main = defaultMain 
    [ bench "No parallelism 200" $ whnf (runGame life) 200 
    , bench "Parallelism 200" $ whnf (runGame parLife) 200 ] 

Zestawione z ghc --make -O2 -o bench i pobiegł z ./bench -o bencht.hmtl +RTS -N3.

Here's the detailed result of the report.

+0

Hmm, dziwne. Dostaję również wynik, że 'parLife' jest szybszy od kryterium, ale kiedy uruchomię to urządzenie samodzielnie,' parLife' jest konsekwentnie znacznie wolniejsze niż "życie". –

+0

Ah, tylko z gwintowanym środowiskiem uruchomieniowym, a nie z nieczytanym! –

+0

Myślę, że ma to coś wspólnego z długowiecznością procesu ... zainicjowanie puli wątków itp. jest droższe niż (oczywiście niewielkie) zyski, które uzyskujemy z równoległości. –