Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何使用Haskell中的策略编写并行约简?_Haskell_Parallel Processing - Fatal编程技术网

如何使用Haskell中的策略编写并行约简?

如何使用Haskell中的策略编写并行约简?,haskell,parallel-processing,Haskell,Parallel Processing,在高性能计算中,求和、积等通常使用“并行缩减”进行计算,该缩减需要n个元素,并在O(logn)时间内完成(给定足够的并行性)。在Haskell中,我们通常使用折叠进行此类计算,但计算时间始终与列表长度成线性关系 Data Parallel Haskell有一些内置的功能,但是在列表的公共框架中呢?我们可以用控制、并行、策略来实现吗 那么,假设f是关联的,我们怎么写呢 parFold::(a->a->a)->[a]->a 因此,parFold f xs只需要长度为的时间对数xs?这似乎是一个好的开

在高性能计算中,求和、积等通常使用“并行缩减”进行计算,该缩减需要n个元素,并在O(logn)时间内完成(给定足够的并行性)。在Haskell中,我们通常使用折叠进行此类计算,但计算时间始终与列表长度成线性关系

Data Parallel Haskell有一些内置的功能,但是在列表的公共框架中呢?我们可以用
控制、并行、策略
来实现吗

那么,假设
f
是关联的,我们怎么写呢

parFold::(a->a->a)->[a]->a


因此,
parFold f xs
只需要长度为
的时间对数xs

这似乎是一个好的开始:

parFold :: (a -> a -> a) -> [a] -> a
parFold f = go
  where
  strategy = parList rseq

  go [x] = x
  go xs = go (reduce xs `using` strategy)

  reduce (x:y:xs) = f x y : reduce xs
  reduce list     = list   -- empty or singleton list

这是可行的,但并行性并不是很好。将
parList
替换为
parListChunks 1000
会有所帮助,但在8核机器上的加速比仍然限制在1.5倍以下。

我认为列表不是适合这种情况的数据类型。因为它只是一个链表,所以必须按顺序访问数据。虽然您可以并行地评估项目,但在缩减步骤中您不会获得太多收益。如果你真的需要一个列表,我认为最好的功能就是

parFold f = foldl1' f . withStrategy (parList rseq)
或许

parFold f = foldl1' f . withStrategy (parBuffer 5 rseq)
如果缩减步骤很复杂,您可以通过如下细分列表获得收益:

parReduce f = foldl' f mempty . reducedList . chunkList . withStrategy (parList rseq)
 where
  chunkList list = let (l,ls) = splitAt 1000 list in l : chunkList ls
  reducedList = parMap rseq (foldl' f mempty)
我冒昧地假设您的数据是mempty的
Monoid
,如果这不可能,您可以用自己的空类型替换mempty,或者更糟糕的情况使用
foldl1'

这里使用了两个来自
Control.Parallel.Strategies
的运算符。
parList
并行计算列表中的所有项。然后,
chunkList
将列表分成1000个元素的块。然后通过
parMap
并行减少这些块中的每个块

你也可以试试

parReduce2 f = foldl' f mempty . reducedList . chunkList
 where
  chunkList list = let (l,ls) = splitAt 1000 list in l : chunkList ls
  reducedList = parMap rseq (foldl' f mempty)
根据工作的具体分布方式,其中一个可能比其他的更有效


如果您可以使用对索引(数组、向量、映射等)有良好支持的数据结构,那么您可以对缩减步骤进行二进制细分,这可能总体上会更好。

不确定您的
parFold
函数应该做什么。如果这是foldr或foldl的并行版本,我认为它的定义是错误的

parFold :: (a -> a -> a) -> [a] -> a

// fold right in haskell (takes 3 arguments)
foldr :: (a -> b -> b) -> b -> [a] -> b
Fold对列表的每个元素应用相同的函数,并累积每个应用的结果。我想,要想实现它的并行版本,需要对元素的函数应用是并行的——有点像
parList
所做的

    par_foldr :: (NFData a, NFData b) => (a -> b -> b) -> b -> [a] -> b
    par_foldr f z [] = z
    par_foldr f z (x:xs) = res `using` \ _ -> rseq x' `par` rdeepseq res
                       where x' = par_foldr f z xs
                             res = x `f` x'

正如人们所注意到的,列表对于递归并行拆分来说是一个糟糕的数据结构。您需要某种二叉树/绳索结构,如Fortress语言:谢谢,John。我喜欢在块上使用foldl。但是,在每个块被缩减后,外部的foldl’是连续的,其输入可能非常大。表达递归的最佳方式是什么?输入可以是列表,也可以不是列表,但这应该可以使用策略来表示。
reducedList
中的
parMap
函数将并行计算所有块。但是,如果您的输入太大,不想一次将其全部加载到内存中,那么您可以使用laziness和parBuffer。我在
parBuffer
方面取得了非常好的成功,因为它允许您利用并行性和惰性。我认为如果您使用
reducedList=withStrategy(parbuffer10rseq),它将起作用。映射(foldl'f mempty)
。我认为这比列表的递归要好,因为可以避免多次遍历。