2012-01-13 11 views
8

Mam funkcję frequencyBy, którą chciałbym zrównoleglić. Tu następuje prosty przypadek testowy:Jak korzystać z równoległych strategii w Haskell

import Control.Parallel.Strategies 
import Control.DeepSeq 
import System.Environment 

frequencyBy :: (a -> b -> Bool) -> [a] -> [b] -> [(a,Int)] 
frequencyBy f as bs = map 
    (\a ->(a, foldr (\b -> if f a b then (+) 1 else id) 0 bs)) as 

main :: IO() 
main = do 
    x:xs <- getArgs 
    let result = frequencyBy (==) [1::Int .. 10000] [1 .. (read x)] `using` 
       parList rdeepseq 
    print $ product $ map snd $ result 

chciałbym uruchomić map w frequencyBy równolegle. Próbuję to osiągnąć, używając parList rdeepseq (wszystkie inne rzeczy w main mają na celu upewnienie się, że nie wszystko jest zoptymalizowane). Jednak to nie działa, dwa wątki wykonują dwukrotnie więcej pracy niż jeden wątek w tym samym czasie. Nie rozumiem, co robię źle tutaj.

+3

Jeśli dwa wątki działają dwa razy więcej niż jeden wątek w tym samym czasie, czy to nie oznacza, że ​​jest to równoznaczne? – ehird

Odpowiedz

9

Może się zdarzyć, że narzut spowalnia działanie systemu, w zależności od tego, jaka jest duża wartość: x; jeśli praca, którą wykonujesz w każdej iskierce, jest porównywalna do czasu potrzebnego na odrodzenie każdej iskry (i oczywiście nie ma narzutu związanego z harmonogramem itp.), wtedy napotkasz problemy.

Możesz spróbować parListChunk, np. parListChunk 64 rdeepseq; będziesz musiał poeksperymentować, aby dowiedzieć się, którego rozmiaru porcji użyć. Podczas gdy twoja obecna strategia tworzy iskrę dla każdego elementu listy, parListChunk tworzy iskrę dla każdej porcji o określonym rozmiarze na liście i używa strategii określonej kolejno dla każdego elementu tej porcji.

Nawiasem mówiąc, foldr w frequencyBy prawdopodobnie spowalnia rzeczy ze względu na nadmierne tworzenie thunk; coś takiego jak

frequencyBy :: (a -> b -> Bool) -> [a] -> [b] -> [(a,Int)] 
frequencyBy f as bs = map (\a -> (a, sum . map (const 1) . filter (f a) $ bs)) as 

powinien to naprawić.

Oczywiście, jak zawsze, upewnij się, że kompilujesz z -O2 i działasz z +RTS -N.

+0

To nie jest ten sam kod; funkcja OP jest równoważna 'sumie. map (const 1) $ filter (f a) bs' lub 'length $ filter (f a) bs', chociaż żadna z nich nie jest dla mnie ulepszeniem (a użycie' length' jest znacznie wolniejsze). –

+0

'parListChunk 2 rdeepseq' już robi lewę i upewnia się, że zajmuje tylko połowę czasu na dwóch wątkach (w porównaniu do jednego wątku). Wydaje się to jednak dziwne, dlaczego ocena kawałków 1 daje dużo kosztów, podczas gdy fragmenty 2 prowadzą do doskonałej równoległości? – user362382

+0

Użyłem 'sumy. map (const 1) $ filter (f a) bs' before, ale odkryłem, że ręczne łączenie go w 'foldr' było znacznie szybsze. – user362382

7

Myślę, że twój paralelizm jest zbyt drobnoziarnisty. parList próbuje ocenić każdy element równolegle, a naprawdę nie ma tak wiele pracy dla jednego elementu.

Po zmianie z parList na parListChunk 500 czas realizacji wzrasta o prawie 50%; ponieważ jestem na dwurdzeniowym komputerze, który jest tak dobry, jak to tylko możliwe.

Dla porównania testowałem pod numerem x=20000.

Powiązane problemy