JKn*_*ght 4 parallel-processing haskell nested vector
与此相关问题类似,我想在Vector上执行并行映射,但在我的情况下,我有一个嵌套的 Vector,我似乎无法使评估正确.
这是我到目前为止:
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Data.Vector.Strategies
import Control.DeepSeq
main = do
let res = genVVec 200 `using` parVector 2
print res
genUVec :: Int -> U.Vector Int
genUVec n = U.map (ack 2) $ U.enumFromN n 75
genVVec :: Int -> V.Vector (U.Vector Int)
genVVec n = V.map genUVec $ V.enumFromN 0 n
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))
instance (NFData a, U.Unbox a) => NFData (U.Vector a) where
rnf = rnf . U.toList
Run Code Online (Sandbox Code Playgroud)
得到:
$ ./vectorPar +RTS -N8 -s >/dev/null
SPARKS: 200 (17 converted, 183 pruned)
Total time 1.37s ( 1.30s elapsed)
$ ./vectorPar +RTS -s >/dev/null
SPARKS: 200 (0 converted, 200 pruned)
Total time 1.25s ( 1.26s elapsed)
Run Code Online (Sandbox Code Playgroud)
我也试过直接修改vector-strategies中的parVector函数,但我的尝试是笨拙无效的.
我意识到REPA是为嵌套工作负载而设计的,但这似乎是一个简单的问题,我宁愿不必重写很多代码.
注意:这里是 vector-strategies 的有罪作者(这是一个很小的标题,因为这只是一个我认为其他人会觉得有用的黑客功能)。
您的观察parVector是错误的,因为它允许在使用之前对火花进行 GCed,这似乎是正确的。SimonM 的建议意味着我必须精确地做我试图避免的事情,以某种代价构建一个新的向量来代替旧的向量。知道这是必要的,几乎没有理由不改用parVector更简单的定义:
parVector2 :: NFData a => Int -> Strategy (V.Vector a)
parVector2 n = liftM V.fromList . parListChunk n rdeepseq . V.toList
Run Code Online (Sandbox Code Playgroud)
请注意,John L 给出的修复仅有效,因为它通过在收集发生之前强制计算来“击败”收集器。
我将更改 vector-strategies 库,因此这是不必要的 - 让您的原始代码正常工作。不幸的是,这会产生上述构建新 Vector 的成本(通常很少)。
问题似乎是parVector不强制评估向量的元素.每个元素都是一个thunk,在向量被消耗(通过打印)之前不会产生任何东西,这对于火花来说太晚了.您可以通过编写parVector策略来强制评估每个元素rdeepseq.
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Data.Vector.Strategies
import Control.DeepSeq
import Control.Parallel.Strategies
main = do
let res = genVVec 200 `using` (rdeepseq `dot` parVector 20)
print res
genUVec :: Int -> U.Vector Int
genUVec n = U.map (ack 2) $ U.enumFromN n 75
genVVec :: Int -> V.Vector (U.Vector Int)
genVVec n = V.map genUVec $ V.enumFromN 0 n
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))
instance (NFData a, U.Unbox a) => NFData (U.Vector a) where
rnf vec = seq vec ()
instance (NFData a) => NFData (V.Vector a) where
rnf = rnf . V.toList
Run Code Online (Sandbox Code Playgroud)
我也改变了你的NFData (U.Vector a)实例.由于a U.Vector是未装箱的,因此对WHNF的评估就足够了,并且通过列表转换强制每个元素是浪费的.实际上,rnf如果您愿意,默认定义可以正常工作.
通过这两个更改,我得到以下内容
SPARKS: 200 (200 converted, 0 pruned)
Run Code Online (Sandbox Code Playgroud)
并且运行时间减少了近50%.我还将矢量块大小调整为20,但结果非常类似于块大小2.