Arrays 如何在一个范围内使用Repa获取数组切片

Arrays 如何在一个范围内使用Repa获取数组切片,arrays,haskell,repa,Arrays,Haskell,Repa,为了计算积分图像,我尝试使用Repa实现一个累积和函数。我当前的实现如下所示: cumsum :: (Elt a, Num a) => Array DIM2 a -> Array DIM2 a cumsum array = traverse array id cumsum' where elementSlice inner outer = slice array (inner :. (0 :: Int)) cumsum' f (inner :

为了计算积分图像,我尝试使用Repa实现一个累积和函数。我当前的实现如下所示:

cumsum :: (Elt a, Num a) => Array DIM2 a -> Array DIM2 a
cumsum array = traverse array id cumsum' 
    where
        elementSlice inner outer = slice array (inner :. (0 :: Int))
        cumsum' f (inner :. outer) = Repa.sumAll $ elementSlice inner outer
问题出在elementSlice函数中。在matlab或numpy中,这可以指定为数组[inner,0:outer]。因此,我要寻找的是以下几点:

slice array (inner :. (Range 0 outer))
但是,似乎不允许在Repa中当前的范围内指定切片。我考虑过使用Haskell中高效并行模板卷积中讨论的分区,但如果每次迭代都会更改分区,那么这似乎是一种相当重要的方法。我也考虑过屏蔽切片(乘以一个二元向量)——但这似乎在大型矩阵上的性能非常差,因为我会为矩阵中的每个点分配一个屏蔽向量


我的问题-有人知道是否有计划增加对Repa范围内切片的支持吗?或者有没有一种性能更好的方法可以解决这个问题,也许可以用另一种方法?

事实上,我认为主要的问题是Repa没有扫描原语。(然而,一个非常相似的库也有。)扫描有两种变体,前缀扫描和后缀扫描。给定一个一维数组

[a_1,…,a_n]

前缀扫描返回

[0,a_0,a_0+a_1,…,a_0+…+a_{n-1}]

而后缀扫描产生

[a_0,a_0+a_1,…,a_0+a_1+…+a_n]

我假设这就是累积和(
cumsum
)函数的目的

前缀和后缀扫描非常自然地推广到多维数组,并且具有基于树缩减的高效实现。关于这个主题的一篇比较老的论文是。此外,科纳尔·埃利奥特(Conal Elliott)最近在哈斯克尔(Haskell)写了一篇关于高效并行扫描的文章

积分图像(在2D阵列上)可以通过进行两次扫描来计算,一次水平扫描,一次垂直扫描。在没有扫描原语的情况下,我实现了一个,效率非常低

horizScan :: Array DIM2 Int -> Array DIM2 Int
horizScan arr = foldl addIt arr [0 .. n - 1]
  where 
    addIt :: Array DIM2 Int -> Int -> Array DIM2 Int
    addIt accum i = accum +^ vs
       where 
         vs = toAdd (i+1) n (slice arr (Z:.All:.i))
    (Z:.m:.n) = arrayExtent arr

--
-- Given an @i@ and a length @len@ and a 1D array @arr@ 
-- (of length n) produces a 2D array of dimensions n X len.
-- The columns are filled with zeroes up to row @i@.
-- Subsequently they are filled with columns equal to the 
-- values in @arr.
--
toAdd :: Int -> Int -> Array DIM1 Int -> Array DIM2 Int
toAdd i len arr = traverse arr (\sh -> sh:.(len::Int)) 
               (\_ (Z:.n:.m) -> if m >= i then arr ! (Z:.n) else 0) 
用于计算积分图像的函数可以定义为

vertScan :: Array DIM2 Int -> Array DIM2 Int
vertScan = transpose . horizScan . transpose

integralImage = horizScan . vertScan

考虑到已经为Accelerate实现了扫描,将其添加到Repa应该不会太难。我不确定使用现有Repa原语的有效实现是否可行。

提取子范围是一种索引空间操作,很容易用fromFunction表示,尽管我们可能应该为它在API中添加更好的包装器

let arr = fromList (Z :. (5 :: Int)) [1, 2, 3, 4, 5 :: Int] 
in  fromFunction (Z :. 3) (\(Z :. ix) -> arr ! (Z :. ix + 1))

> [2,3,4]
结果中的元素通过偏移提供的索引并从源中查找来检索。这种技术自然扩展到更高秩的数组


关于实现并行折叠和扫描,我们将通过向库中添加一个原语来实现。我们不能根据map定义并行缩减,但我们仍然可以使用延迟数组的总体方法。这将是一个合理的正交扩展。

如果有人提出了一个高效的Repa扫描实现,我会非常惊讶。Repa对数组使用的模型使得所有元素都可以独立计算。但对于扫描,每个元素都依赖于前一个元素,至少你们希望能够高效地计算它。因此,我相信您必须想出与Repa完全不同的方法来支持高效扫描。谢谢,我非常感谢您的回答!扫描完全符合我的需要,我实际上已经尝试实现了一个类似于Repa库本身实现折叠的方法,但是遇到了问题,继续讨论问题中给出的代码。不管怎样,我都通读了那篇文章和科纳尔的帖子——非常感谢这些链接。我还没有完全放弃,我将继续阅读Repa source,看看我能想出什么。至少我现在可以使用效率低下的版本,以后再进行优化。@svenningsson。实际上不,这是扫描的有趣之处。如果运算符是二进制关联的,那么有一种有效的方法可以使用多个处理器(尽管有一些冗余)。想象一下将数字1和8相加的简单任务。使用一个处理器,您可以在运行过程中累积结果,用四个处理器依次保持值1、3、6、10等。第1关:第1关和第1关和第2关和第3关和第4关,第3关和第5关和第6关,第4关和第7关和第8关。通过2。1号加3号和7号,2号加11号和15号。(第三和第四个未使用)。通过3:添加10和26。这是一个对数步数。扫描更复杂,但类似。我很清楚,高效地并行扫描是可能的。我的观点是,我不认为Repa使用的模型允许这样的实现。Ben,我只是想感谢你的回答。我现在有一个解决方案,看起来像。剩下的就是执行边界检查并将其推广到多个维度。我在想一些类似于扩展的cumsum(Z:.All:.All)arr的东西,以便于跨多个维度求和(而不是我目前使用的转置解决方案)。