【问题标题】:How to write parallel code with Haskell vectors?如何用 Haskell 向量编写并行代码?
【发布时间】:2010-11-15 17:04:43
【问题描述】:

一方面,在 Haskell 中,Vector a 似乎是用作数字数组的首选类型。甚至还有一个(不完整的)Vector Tutorial

另一方面,Control.Parallel.Strategies 主要是根据Traversable 定义的。向量库不提供这些实例。

Traversable t 的最小完整定义也应该定义Foldable

traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
sequenceA :: Applicative f => t (f a) -> f (t a)

我看不出如何为Data.Vector.Unboxed.Vector 定义sequenceA。那么,用未装箱的向量编写并行代码的最佳方法是什么?定义一些新的临时策略,如 evalVector 或显式使用 parpseq 或使用纯 Data.Array 而不是向量?

附:普通的Arrays 可以毫无问题地并行化:https://gist.github.com/701888

【问题讨论】:

  • 有点急于 DPH 展示一些成果,是吗?
  • 嗯,有点。我想尝试在 Haskell 中编写数字代码,但还不明白我应该使用什么。
  • 我认为您的 parVector 版本行不通:rseq 不会评估任何元素(它唯一的 WHNF),V.concat 是不必要的 O(n) 操作 -我们试图强制计算元素,不需要构造一个新的向量。
  • vLen - half 也应该是 vLen - 1,但这也不完全正确,因为它对我来说是段错误。
  • 我将它作为包 vector-strategies 上传,因为它对广大观众来说似乎足够有用,但 Roman 不愿意添加到 Vector build-deps(可以理解)。

标签: haskell vector parallel-processing


【解决方案1】:

这对parVector 来说是一项黑客工作,但这对我有用:

import qualified Data.Vector as V
import Control.Parallel.Strategies
import Control.Parallel
import Control.DeepSeq

ack :: Int -> Int -> Int
ack 0 n = n+1
ack m 0 = ack (m-1) 1
ack m n = ack (m-1) (ack m (n-1))

main = do
  let vec = V.enumFromN 1 1000
  let res = (V.map (ack 2) vec) `using` parVector
  print res

parVector :: NFData a => Strategy (V.Vector a)
parVector vec = eval vec `seq` Done vec
  where
  chunkSize = 1
  eval v
    | vLen == 0 = ()
    | vLen <= chunkSize = rnf (v V.! 0) -- FIX this to handle chunks > 1
    | otherwise = eval (V.take half v) `par` eval (V.drop half v)
    where vLen = V.length v
          half = vLen `div` 2

并运行此代码:

[tommd@Mavlo Test]$ ghc --make -O2 -threaded t.hs
... dumb warning ...
[tommd@Mavlo Test]$ time ./t +RTS -N1 >/dev/null
real    0m1.962s user    0m1.951s sys     0m0.009s
[tommd@Mavlo Test]$ time ./t +RTS -N2 >/dev/null
real    0m1.119s user    0m2.221s sys 0m0.005s

当我在类型签名中使用Integer 而不是Int 运行代码时:

[tommd@Mavlo Test]$ time ./t +RTS -N2 >/dev/null

real    0m4.754s
user    0m9.435s
sys     0m0.028s
[tommd@Mavlo Test]$ time ./t +RTS -N1 >/dev/null

real    0m9.008s
user    0m8.952s
sys     0m0.029s

摇滚!

编辑:更接近您之前尝试的解决方案更简洁(它不使用来自三个独立模块的函数)并且效果很好:

parVector :: NFData a => Strategy (V.Vector a)
parVector vec =
  let vLen = V.length vec
      half = vLen `div` 2
      minChunk = 10
  in  if vLen > minChunk
      then do
        let v1 = V.unsafeSlice 0 half vec
            v2 = V.unsafeSlice half (vLen - half) vec
        parVector v1
        parVector v2
        return vec
      else
        evalChunk (vLen-1) >>
        return vec
  where
  evalChunk 0 = rpar (rdeepseq (vec V.! 0)) >> return vec
  evalChunk i = rpar (rdeepseq (vec V.! i)) >> evalChunk (i-1)

从这个解决方案中学到的东西:

  1. 它使用 Eval monad,这是严格的,所以我们肯定会激发一切(与将东西包装在 let 中并记住使用 bang 模式相比)。
  2. 与您提出的实现相反,它 (a) 不构造新向量,这很昂贵 (b) evalChunk 强制使用 rparrdeepseq 评估每个元素(我不相信 @987654334 @ 强制向量的任何元素)。
  3. 与我的看法相反,slice 采用开始索引和长度,而不是开始和结束索引。糟糕!
  4. 我们仍然需要导入 Control.DeepSeq (NFData),但我已通过电子邮件发送库列表以尝试解决该问题。

性能似乎与此答案中的第一个 parVector 解决方案相似,因此我不会发布数字。

【讨论】:

【解决方案2】:

1) 您可能知道,vectorDPH 工作的产物,该工作已被证明比研究人员最初预期的要难。

2) 未装箱的向量无法跨多个 CPU 划分单个元素的工作。

3) 我对盒装矢量更有希望。比如:

using (map (rnf . (vec !)) [0..V.length vec - 1]) (parList rdeepseq)

或者,也许您可​​以避免构建列表并使用 parlist。我认为只分配数组的一部分就足够了。下面的代码可能被破坏了,但是使用rnf 制作自己的parVector 并将向量分成两半直到它成为单个元素(或一些可调整的元素块大小)的概念应该可行。

parVector :: Strategy (Vector a)
parVector = let !_ = eval vec in Done vec
  where
  chunkSize = 1
  eval v
    | vLen == 0 = ()
    | vLen <= chunkSize = rnf (v ! 0) -- FIX this to handle chunks > 1
    | otherwise = eval (V.take half v) `par` eval (V.drop half v)
    where vLen = V.length v
          half = vLen `div` 2

【讨论】:

  • 汤姆,谢谢你的想法。我会试试看。我是否理解正确,即使这个 parVector 也不能用于未装箱的向量?
  • 对。也许 Roman(vector 的作者,有时在这里发帖)会进来让事情变得更清晰,但未装箱的数据不能是延迟计算(不能是 thunk)。强制未装箱矢量的任何元素强制其他元素,并且任何并行性都必须在 Vector 包内完成(如果可能的话)。
  • 我尝试了parVector 策略,尽管我不得不重写它以使用更新的parallel 构建它(请参阅编辑后的问题)。不幸的是,它没有提供任何加速。
猜你喜欢
  • 2020-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多