【发布时间】:2011-10-19 17:06:10
【问题描述】:
还有一个对应的称为密度数组。这是什么意思?我做了一些搜索,但没有得到准确的信息。
【问题讨论】:
标签: arrays data-structures sparse-matrix
还有一个对应的称为密度数组。这是什么意思?我做了一些搜索,但没有得到准确的信息。
【问题讨论】:
标签: arrays data-structures sparse-matrix
大踏步就是“迈出一大步”
对于一个数组,这意味着只有一些元素存在,比如每 10 个元素。然后,您可以通过不在两者之间存储空元素来节省空间。
密集数组是指存在许多(如果不是全部)元素的数组,因此元素之间没有空白空间。
【讨论】:
<valarray> 部分。
假设你有一个结构
struct SomeStruct {
int someField;
int someUselessField;
int anotherUselessField;
};
还有一个数组
struct SomeStruct array[10];
然后如果你查看这个数组中的所有someFields,它们本身可以被认为是一个数组,但它们不占用后续的内存单元,所以这个数组是跨步的。这里的 stride 是sizeof(SomeStruct),即跨步数组的两个后续元素之间的距离。
这里提到的稀疏数组是一个更通用的概念,实际上是一个不同的概念:跨步数组在跳过的内存单元中不包含零,它们只是不是数组的一部分。
跨步数组是stride != sizeof(element)时常用(密集)数组的泛化。
【讨论】:
如果要对二维数组的子集进行操作,则需要知道数组的“步幅”。假设你有:
int array[4][5];
并且您想要对从数组[1][1] 到数组[2,3] 开始的元素子集进行操作。 形象地说,这是下图的核心:
+-----+-----+-----+-----+-----+
| 0,0 | 0,1 | 0,2 | 0,3 | 0,4 |
+-----+=====+=====+=====+-----+
| 1,0 [ 1,1 | 1,2 | 1,3 ] 1,4 |
+-----+=====+=====+=====+-----+
| 2,0 [ 2,1 | 2,2 | 2,3 ] 2,4 |
+-----+=====+=====+=====+-----+
| 3,0 | 3,1 | 3,2 | 3,3 | 3,4 |
+-----+-----+-----+-----+-----+
要在函数中准确访问数组的子集,需要告诉被调用函数数组的步长:
int summer(int *array, int rows, int cols, int stride)
{
int sum = 0;
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
sum += array[i * stride + j];
return(sum);
}
和电话:
int sum = summer(&array[1][1], 2, 3, 5);
【讨论】:
在高度优化的代码中,一种相当常见的技术是将填充插入到数组中。这意味着第 N 个逻辑元素不再位于偏移量N*sizeof(T)。这可能是一种优化的原因是某些缓存是关联性受限的。这意味着他们不能为某些对 i,j 缓存 array[i] 和 array[j]。如果在密集数组上运行的算法会使用许多这样的对,则插入一些填充可能会减少这种情况。
发生这种情况的常见情况是在图像处理中。图像通常具有 512 字节或另一个“二进制整数”的线宽,并且许多图像处理例程使用像素的 3x3 邻域。结果,您可以在某些缓存体系结构上获得相当多的缓存驱逐。通过在每行的末尾插入“奇怪”数量的假像素(例如 3 个),您可以更改“步幅”并且相邻行之间的缓存干扰更少。
这是非常特定于 CPU 的,因此这里没有一般建议。
【讨论】:
我在这里添加另一个答案,因为我没有找到任何令人满意的现有答案。
Wikipedia 解释了 stride 的概念,并写道“stride 不能小于元素大小(这意味着元素重叠)但可以更大(表示元素之间有额外的空间) )”。
但是,根据我发现的信息,跨步数组 完全可以做到这一点:通过允许跨度为零或负数来节省内存。
Compiling APL to JavaScript 将跨步数组解释为一种同时使用数据和跨度来表示多维数组的方法,这与假定隐式跨度为 1 的数组的典型“矩形”表示不同。它允许正跨度、负跨度和零跨度。为什么?它允许许多操作只改变步幅和形状,而不是底层数据,从而允许有效地操作大型数组。
在处理大量数据时,这种跨步表示的优势变得显而易见。 transpose (
⍉⍵)、reverse (⌽⍵) 或 drop (⍺↓⍵) 等函数可以重用数据数组,并且只需要为其结果赋予新的形状、步幅和偏移量。重新整形的标量,例如1000000⍴0,只能占用一定的内存,利用strides可以为0的事实。
我还没有弄清楚这些操作将如何实现为跨步和形状上的操作,但很容易看出,仅更改这些而不是基础数据在计算方面会便宜得多。但是,值得记住的是,跨步表示可能会对缓存局部性产生负面影响,因此根据用例,使用常规矩形数组可能会更好。
【讨论】:
可能性一:Stride 描述一个缓冲数组来读取一个优化的数组
当你使用一种方法来 store multidimensional arrays in linear storage。步幅描述了缓冲区每个维度的大小,这将帮助您读取该数组。图片取自Nd4j (More info about Stride)
可能性2(较低级别):步幅是数组的连续成员之间的距离
这意味着索引为 0 和 1 的项目的地址在内存中不会是连续的,除非您使用单位步长。值越大,项目在内存中的距离就越远。
这在低级别很有用(字长优化、重叠数组、缓存优化)。参考wikipedia。
【讨论】: