一方面,在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)
我看不出如何为sequenceA
定义Data.Vector.Unboxed.Vector
。那么,使用未装箱的向量编写并行代码的最佳方法是什么?定义一些新的临时策略,例如evalVector
或明确使用par
和pseq
或使用普通Data.Array
代替向量?
P.S。普通Array
可以并行化而没有问题:https://gist.github.com/701888
答案 0 :(得分:6)
这是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)
从这个解决方案中学到的东西:
Eval
monad,这是严格的,所以我们肯定会引发一切(与在let
中包装并记住使用爆炸模式相比)。evalChunk
强制使用rpar
和rdeepseq
评估每个元素(I不要相信rpar vec
强制任何向量的元素。)slice
采用起始索引和长度,而不是起始和结束索引。糟糕!Control.DeepSeq (NFData)
,但我已通过电子邮件发送图书馆列表以尝试解决该问题。性能与此答案中的第一个parVector
解决方案类似,因此我不会发布数字。
答案 1 :(得分:2)
1)正如您可能知道的那样,vector
是DPH作品的产物,其证明比研究人员最初的预期更难。
2)未装箱的向量不能将多个CPU的各个元素的工作分开。
3)我对盒装载体更有希望。类似的东西:
using (map (rnf . (vec !)) [0..V.length vec - 1]) (parList rdeepseq)
或许你可以避免构建列表并使用parlist。我认为只分配数组的部分就足够了。下面的代码可能已经破解了,但是使用parVector
创建自己的rnf
并将向量分成两半直到它是单个元素(或一些可调整的元素块大小)的概念应该有效。< / p>
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