修复3'性能和正确使用'现在'

时间:2012-03-15 02:56:28

标签: performance haskell profiling monads repa

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

我正在使用Repa3工作。我无法获得高效的并行代码。如果我让我的函数返回延迟数组,我会得到极其缓慢的代码,可以很好地扩展到8个内核。此代码每GHC探查器占用超过20GB的内存,并且运行速度比基本Haskell未装箱的向量慢几个数量级。

或者,如果我让所有函数返回Unboxed清单数组(仍尝试在函数中使用fusion,例如当我执行'map')时,我获得了更快的代码(仍然比使用Haskell未装箱的向量慢) )它根本不会扩展,实际上随着核心数量的增加会略微变慢。

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

FFT代码也充分利用了'now'功能。但是,当我尝试在我的代码中使用它时出现类型错误:

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

代码在没有'now'的情况下编译得很好。使用'now',我收到以下错误:

  

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

我不认为 this是我的问题。如果有人可以解释Monad如何在“现在”工作,将会很有帮助。根据我的最佳估计,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)

更好地了解何时使用'now'会很有帮助。

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

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

2 个答案:

答案 0 :(得分:8)

以下是now的源代码:

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

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

在您的代码中,now不适用,因为您不在monad中。但在这种情况下,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

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

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

值得庆幸的是,extend会产生一个延迟数组,因为要解决所有这些问题会很麻烦。

延迟阵列也允许融合的可能性,如果阵列显而易见,这是不可能的。

最后,computeUnboxedP只是computeP,具有特殊类型。明确地给予computeUnboxedP可能会使GHC更好地进行优化,并使代码更清晰。

答案 1 :(得分:2)

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