重新3表现和正确使用“现在”

这里有一个基本的monad问题,与Repa无关,再加上几个Repa的具体问题。

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

或者,如果我让所有函数都返回Unboxed清单数组(仍尝试在函数中使用融合,例如当我执行'映射'时),我会得到更快的代码(比使用Haskell unboxed矢量更慢),它不会根本没有规模,事实上,随着核心数量的增加,其速度会变慢。

基于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

代码编译良好,没有'现在'。 随着'现在',我得到以下错误:

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

我不认为这是我的问题。 如果有人能够解释Monad如何在“现在”工作,那将会很有帮助。 据我最好的估计,monad似乎在创造一个'Arr U(Arr Ur)'。 我期待着一个'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数组以及用于整数/盒装类型的其他数组?

我意识到这里有很多问题,我很欣赏任何/所有的答案!


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


这里是now的源代码:

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

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

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

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

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

由于y指的是x ,所以我们希望在开始计算y之前确定x 。 如果没有,那么可用的工作将不会在这个团队中正确分配。 为了解决这个问题,最好把y写成

y = deepSeqArray x . computeUnboxedP $ map f x

现在,对于一个延迟阵列,我们有

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

而不是计算所有元素,这只是使确保该形状被计算,并且降低f到弱头正常形式。

至于清单vs延迟数组,肯定有时间延迟数组是更可取的。

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 。 明确提供computeUnboxedP可能会使GHC更好地优化,并使代码更清晰一些。

链接地址: http://www.djcxy.com/p/59971.html

上一篇: Repa 3 performance and correct usage of 'now'

下一篇: Best way of "looping over a 2