重新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更好地优化,并使代码更清晰一些。