2016-12-17 3 views
0

Une question sur le fait de rendre le travail de parallélisme efficace, a demandé un débutant Haskell.Apprivoiser le parallélisme dans Haskell/GHC

Le défi Advent of Code Day 14 consiste à créer des hachages MD5 d'une séquence d'entiers, en recherchant les n premiers entiers qui donnent des hachages remplissant certaines propriétés. Je le fais essentiellement en créant les hachages puis en les filtrant. Je pensais que ce serait une bonne chose à essayer avec le parallélisme, en utilisant plusieurs cœurs pour générer les hachages.

La version non-parallèle de la création de hachage ressemble à ceci:

md5sequenceS :: [String] 
md5sequenceS = [makeMd5 i | i <- [0..]] 
    where makeMd5 i = stretch $ getHash (salt ++ show i) 
      stretch h0 = foldr (\_ h -> getHash h) h0 [1..2016] 

... et il fonctionne très bien, bien que lentement, donnant une réponse dans environ quatre minutes.

La version parallèle ressemble à ceci:

md5sequenceS :: [String] 
md5sequenceS = parMap rdeepseq (makeMd5) [0..] 
    where makeMd5 i = stretch $ getHash (salt ++ show i) 
      stretch h0 = foldr (\_ h -> getHash h) h0 [1..2016] 

... le même que précédemment à part le bit parMap rdeepseq. Cela ne fonctionne pas bien: il consomme toute la mémoire disponible sur ma machine, et ne parvient toujours pas à produire une réponse après 30 minutes de temps de mur. Il utilise cependant tous les processeurs complètement.

Que dois-je faire pour maîtriser ce parallélisme incontrôlé?

(La spécification problème ne donne aucune idée de combien de hash je dois générer, mais il se trouve que je besoin d'environ 30 000 entiers hachés.)

Modifier pour inclure réponse acceptée

la stratégie parBuffer peut être utilisé comme

md5sequenceS = withStrategy (parBuffer 100 rdeepseq) $ map (makeMd5) [0..] 
    where makeMd5 i = stretch $ getHash (salt ++ show i) 
      stretch h0 = foldr (\_ h -> getHash h) h0 [1..2016] 

Performance est pas grande par rapport à la version mono-thread, mais c'est une autre question ...

Répondre

3

parMap va forcer l'évaluation de toute la liste, ce qui dans votre cas est infini. Au lieu d'utiliser parMap, vous pouvez envisager d'utiliser d'autres stratégies telles que parBuffer qui peut gérer des listes infinies.

+1

Cela l'expliquerait, merci! Réécrire la version parallèle comme 'md5sequenceS = withStrategy (parBuffer 100 rdeepseq) $ map (makeMd5) [0 ..]' fait l'affaire. Temps de mur est maintenant 3min 44sec avec 12 cœurs, contre 5min 55sec pour la version mono-processus, mais c'est un autre problème ... –