2012-01-13 49 views
8

使用并行策略我有一个函数frequencyBy,我想并行。这里有个简单的测试案例:如何在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 

我想在frequencyBy并行运行的map。我试图使用parList rdeepseq来实现这一点(main中的所有其他内容仅用于确保不是所有内容都得到了优化)。但是,这不起作用,两个线程的工作量是一个线程在同一时间内的两倍。我不明白我在这里做错了什么。

+3

如果两个线程做两倍的同时多的工作,一个线程,并不意味着它是正确parallelising? – ehird 2012-01-13 14:57:59

回答

9

这可能是开销放慢下来,这取决于有多大X是;如果你在每个火花中所做的工作与产生每个火花所花费的时间相当(当然还有计划开销等),那么你会遇到问题。

你可以尝试parListChunk,例如parListChunk 64 rdeepseq;您将不得不尝试确定要使用哪个块大小。尽管当前策略正在为列表中的每个元素创建一个火花,但是parListChunk会为列表中的某个大小的每个块创建一个火花,并使用您在该块的每个元素上按顺序指定的策略。

顺便说一句,frequencyBy中的foldr可能由于过多的thunk创建而放慢速度;像

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

应该解决这个问题。

当然,一如既往,确保你与-O2编译并与+RTS -N运行。

+0

这是不一样的代码; OP的功能相当于总和。 map(const 1)$ filter(f a)bs'或'length $ filter(f a)bs',虽然对我来说这两方面都没有改进(并且使用'length'的速度要慢得多)。 – 2012-01-13 15:17:10

+0

'parListChunk 2 rdeepseq'已经完成了这个技巧,并且确保它在两个线程上只花费了一半的时间(与一个线程相比)。然而,这看起来很奇怪,为什么评估1的块会带来很多开销,而2块则会导致完美的并行化? – user362382 2012-01-13 15:17:19

+0

我用'sum。 map(const 1)$ filter(f a)bs'之前,但我发现手动将它融合到一个'foldr'中的速度更快。 – user362382 2012-01-13 15:19:28

7

我认为你的并行性太细。 parList尝试并行评估每个元素,并且对于任何一个元素确实没有太多的工作。

当我从parList更改为parListChunk 500近50%的执行时间的增加;因为我在双核机器上的性能差不多。

仅供参考,我与x=20000测试。