这是一个针对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)
| 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使用rpar和rdeepseq强制评估每个元素(我不认为rpar vec会强制执行任何向量的元素)。
3.与我的信念相反,slice需要一个起始索引和长度,而不是起始和结束索引。哎呀!
4.我们仍然需要导入Control.DeepSeq(NFData),但我已经向库列表发送了电子邮件以尝试解决该问题。
性能看起来类似于这个答案中的第一个parVector解决方案,因此我不会发布数字。
rseq
不会评估任何元素(它只是WHNF),而V.concat
是不必要的O(n)操作 - 我们正在尝试强制计算元素,没有必要构造新向量。 - Thomas M. DuBuissonvLen - half
可能应该是vLen - 1
,但这也不完全正确,因为它会在我的计算机上出现段错误。 - Thomas M. DuBuisson