这里有一个基本的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类型创建其他数组?
我意识到这里有很多问题,我感谢任何/所有答案!
答案 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包还包含一个新的矩阵乘法实现,用于演示它们的用法。