Haskell嵌套向量并行策略

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是为嵌套工作负载而设计的,但这似乎是一个简单的问题,我宁愿不必重写很多代码.

Tho*_*son 6

注意:这里是 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 的成本(通常很少)。


Joh*_*n L 5

问题似乎是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.