15

这里有一个基本的 monad 问题,与 Repa 无关,还有几个 Repa 特定的问题。

我正在使用 Repa3 开发一个库。我无法获得高效的并行代码。如果我让我的函数返回延迟的数组,我会得到非常慢的代码,可以很好地扩展到 8 个内核。这段代码每个 GHC 分析器占用超过 20GB 的内存,运行速度比基本的 Haskell 未装箱向量慢几个数量级。

或者,如果我让所有函数都返回未装箱清单数组(仍然尝试在函数中使用融合,例如当我执行“映射”时),我会得到更快的代码(仍然比使用 Haskell 未装箱向量慢)根本无法扩展,事实上随着内核的增多,速度会变得稍微慢一些。

基于 Repa-Algorithms 中的 FFT 示例代码,似乎正确的方法是始终返回清单数组。有没有我应该返回延迟数组的情况?

FFT 代码还大量使用了“现在”功能。但是,当我尝试在代码中使用它时出现类型错误:

type Arr t r = Array t DIM1 r
data CycRingRepa m r = CRTBasis (Arr U r)
                     | PowBasis (Arr U r)

fromArray :: forall m r t. (BaseRing m r, Unbox r, Repr t r) => Arr t r -> CycRingRepa m r
fromArray = 
    let mval = reflectNum (Proxy::Proxy m)
    in \x ->
        let sh:.n = extent x
        in assert (mval == 2*n) PowBasis $ now $ computeUnboxedP $ bitrev x

没有“现在”,代码编译得很好。使用“现在”,我收到以下错误:

无法匹配类型r' with数组 U (Z :. Int) r' `r' 是一个刚性类型变量,受 fromArray :: (BaseRing mr, Unbox r, Repr tr) => Arr tr -> CycRingRepa mr 的类型签名约束在 C:\Users\crockeea\Documents\Code\LatticeLib\CycRingRepa.hs:50:1 预期类型:CycRingRepa mr 实际类型:CycRingRepa m (Array U DIM1 r)

我不认为 是我的问题。如果有人可以解释 Monad 在“现在”中的工作方式,那将会很有帮助。根据我的最佳估计,单子似乎正在创建一个“Arr U (Arr U r)”。我期待一个“Arr U r”,它将匹配数据构造函数模式。发生了什么事,我该如何解决?

类型签名是:

computeUnboxedP :: Fill r1 U sh e => Array r1 sh e -> Array U sh e
now :: (Shape sh, Repr r e, Monad m) => Array r sh e -> m (Array r sh e)

更好地了解何时使用“现在”会有所帮助。

其他几个 Repa 问题:我应该显式调用 computeUnboxedP(如 FFT 示例代码中所示),还是应该使用更通用的 computeP(因为 unbox 部分是由我的数据类型推断的)?我应该将延迟数组或清单数组存储在数据类型 CycRingRepa 中吗?最终,我还希望这段代码与 Haskell Integers 一起使用。这是否需要我编写使用 U 数组以外的东西的新代码,或者我是否可以编写多态代码来为 unbox 类型创建 U 数组和为 Integers/boxed 类型创建其他数组?

我意识到这里有很多问题,我感谢任何/所有答案!

4

2 回答 2

8

这是源代码now

now arr = do
  arr `deepSeqArray` return ()
  return arr

所以它实际上只是deepSeqArray. 您可以使用其中任何一个来强制评估,而不是挂在一个 thunk 上。这种“评估”与调用时强制的“计算”不同computeP

在您的代码中,now不适用,因为您不在单子中。但在这种情况下deepSeqArray也无济于事。考虑这种情况:

x :: Array U Int Double
x = ...

y :: Array U Int Double
y = computeUnboxedP $ map f x

由于y是指x,我们想x在开始计算之前确定是计算的y。否则,可用工作将无法在线程群中正确分配。为了解决这个问题,最好y写成

y = deepSeqArray x . computeUnboxedP $ map f x

现在,对于延迟数组,我们有

deepSeqArray (ADelayed sh f) y = sh `deepSeq` f `seq` y

而不是计算所有元素,这只是确保计算形状,并减少f到弱头正常形式。

至于清单与延迟数组,当然有时间延迟数组是更可取的。

multiplyMM arr brr
 = [arr, brr] `deepSeqArrays`
   A.sumP (A.zipWith (*) arrRepl brrRepl)
 where  trr             = computeUnboxedP $ transpose2D brr
        arrRepl         = trr `deepSeqArray` A.extend (Z :. All   :. colsB :. All) arr
        brrRepl         = trr `deepSeqArray` A.extend (Z :. rowsA :. All   :. All) trr
        (Z :. _     :. rowsA) = extent arr
        (Z :. colsB :. _    ) = extent brr

这里“扩展”通过在一组新维度上复制值来生成一个新数组。特别是,这意味着

arrRepl ! (Z :. i :. j :. k) == arrRepl ! (Z :. i :. j' :. k)

谢天谢地,extend产生了一个延迟的数组,因为经历所有这些复制的麻烦是一种浪费。

延迟阵列还允许融合的可能性,如果阵列是明显的,这是不可能的。

最后,computeUnboxedP只是computeP一个专门的类型。显式提供可能会让GHCcomputeUnboxedP更好地优化,并使代码更清晰一些。

于 2012-03-18T01:56:01.363 回答
2

Repa 3.1 不再需要显式使用now. 并行计算函数都是一元的,并自动应用于deepSeqArray它们的结果。repa -examples包还包含一个新的矩阵乘法实现,演示了它们的使用。

于 2012-04-10T03:40:15.137 回答