存储多维数组/张量的最佳方法

时间:2011-08-02 08:52:58

标签: scala

我正在尝试在scala中创建一个张量(可以被设想为多维数组)包。到目前为止,我将数据存储在1D Vector中并进行索引算法。

但切片和子阵列并不容易获得。人们需要做很多算术才能将多维索引转换为1D索引。

有没有存储多维数组的最佳方法?如果没有,即一维数组是最好的解决方案,那么如何最佳地对数组进行切片(一些具体的代码对我有帮助)?

5 个答案:

答案 0 :(得分:5)

回答这个问题的关键是:什么时候指针间接比算术更快?答案几乎从来没有。对于2D,有序遍历的速度大致相同,事情会变得更糟:

2D random access
  Array of Arrays - 600 M / second
  Multiplication - 1.1 G / second

3D in-order
  Array of Array of Arrays - 2.4G / second
  Multiplication - 2.8 G / second

(etc.)

所以你最好只做数学。

现在的问题是如何进行切片。最初,如果您的尺寸为n1,n2,n3,...以及i1,i2,i3,...的索引,则通过

计算数组的偏移量
i = i1 + n1*(i2 + n2*(i3 + ... ))

其中通常选择i1作为 last (最里面)维度(但通常​​它应该是最里面循环中最常见的维度)。也就是说,如果它是一个(...)数组的数组,你可以将它作为a(...)(i3)(i2)(i1)索引。

现在假设您要切片。首先,您可以为每个索引提供偏移量o1,o2,o3:

i = (i1 + o1) + n1*((i2 + o2) + n2*((i3 + o3) + ...))

然后你会有一个较短的范围(让我们称之为m1,m2,m3,......)。

最后,如果你完全消除一个维度 - 比方说,m2 == 1,意思是i2 == 0,你只需简化公式:

i = (i1 + o1 + n1*o2) + (n1+n2)*((i3 + o3) + ... ))

我会将它作为练习留给读者来弄清楚如何执行此操作,但请注意我们可以存储新的常量o1 + n1*o21n1+n2,因此我们无需保留在片上做数学。

最后,如果你允许任意尺寸,你只需将该数学放入while循环中。不可否认,这确实会让它减慢一点,但你仍然至少和你使用指针取消引用一样好(几乎在所有情况下)。

答案 1 :(得分:1)

根据我自己的一般经验:如果你必须自己编写一个多维(矩形)数组类,不要将数据存储为Array[Array[Double]],而是使用一维存储并添加辅助方法来转换多维访问元组到一个简单的索引,反之亦然。

当使用列表列表时,您需要做很多记账,所有列表都具有相同的大小,并且在将子列表分配给另一个子列表时需要小心(因为这使得分配给子列表与第一个子列表相同)并且您想知道为什么更改(0,5)上的项目也会更改(3,5))。

当然,如果您希望某个维度的切片频率比另一个维度更高,并且您希望同样具有该维度的引用语义,那么列表列表将是更好的解决方案,因为您可以传递这些内部列表作为消费者的切片而不进行任何复制。但是,如果您不希望这样,那么为切片添加代理类是更好的解决方案,切片映射到多维数组(后者又映射到一维存储数组)。

答案 2 :(得分:1)

只是一个想法:使用Int-tuples作为键的地图怎么样? 例如:

val twoDimMatrix = Map((1,1) -> -1, (1,2) -> 5, (2,1) -> 7.7, (2,2) -> 9)

然后你可以

scala> twoDimMatrix.filterKeys{_._2 == 1}.values 
res1: Iterable[AnyVal] = MapLike(-1, 7.7)

twoDimMatrix.filterKeys{tuple => { val (dim1, dim2) = tuple; dim1 == dim2}} //diagonal

这样,索引算术将由地图完成。我不知道这是多么实用和快速。

答案 3 :(得分:0)

只要在设计之前已知维数,就可以使用集合的集合...(n次)。如果你必须能够为任意数量的维度构建一个verctor,那么scala API就没有什么方便的了(据我所知)。

答案 4 :(得分:0)

您可以简单地将信息存储在多维数组中(例如。“Array [Array [Double]])。

如果张量很小并且可以适合缓存,那么由于数据存储位置的原因,您可以通过1D阵列提高性能。复制整个张量也应该更快。

用于切片算术。这取决于你需要什么样的切片。我想你已经有了一个基于索引提取元素的函数。因此,基于索引迭代编写基本拼接循环,手动插入表达式以提取元素,然后尝试简化整个循环。通常比从头开始编写正确的表达式更简单。