【问题标题】:What is a strided array?什么是跨步数组?
【发布时间】:2011-10-19 17:06:10
【问题描述】:

还有一个对应的称为密度数组。这是什么意思?我做了一些搜索,但没有得到准确的信息。

【问题讨论】:

    标签: arrays data-structures sparse-matrix


    【解决方案1】:

    大踏步就是“迈出一大步”

    thefreedictionary.com/stride

    对于一个数组,这意味着只有一些元素存在,比如每 10 个元素。然后,您可以通过不在两者之间存储空元素来节省空间。

    密集数组是指存在许多(如果不是全部)元素的数组,因此元素之间没有空白空间。

    【讨论】:

    • 我知道的术语是“稀疏数组”(我实际上听说过“稀疏矩阵”或“稀疏向量”,尤其是数字)。 “跨步数组”是“稀疏数组”的同义词吗?
    • “稀疏数组”可能是一个更常见的术语,但含义可能略有不同(不是元素之间的常规距离?)。术语 stride 确实出现在 C++ 标准的<valarray> 部分。
    • @Kos:稀疏数组可以有更一般的形状。这确实是稀疏数组的一个特例。
    • @Alexandre,不,跨步数组的跨度可以不是元素大小的倍数。而且这些概念在逻辑上是不同的
    • @unkulunkulu:例如,当您使用 1/2 级 BLAS 时,您将数组的步幅作为参数传递,它是一个数字。 stride 的大多数用法都假设一个恒定的步幅,即要跳过的元素数加一。
    【解决方案2】:

    假设你有一个结构

    struct SomeStruct {
        int someField;
        int someUselessField;
        int anotherUselessField;
    };
    

    还有一个数组

    struct SomeStruct array[10];
    

    然后如果你查看这个数组中的所有someFields,它们本身可以被认为是一个数组,但它们不占用后续的内存单元,所以这个数组是跨步的。这里的 stridesizeof(SomeStruct),即跨步数组的两个后续元素之间的距离。

    这里提到的稀疏数组是一个更通用的概念,实际上是一个不同的概念:跨步数组在跳过的内存单元中不包含零,它们只是不是数组的一部分。

    跨步数组是stride != sizeof(element)时常用(密集)数组的泛化。

    【讨论】:

    • 好吧,实际上,他们说 strides 可以是非常量的,我忽略了这一点:(
    • 所以我又糊涂了。如果 sparse 与 stride 之间的区别不是“距离”是否相等,那么什么是?
    • @Kos,这有点合乎逻辑:稀疏数组只是通过仅列出数组的非零元素及其索引来减少内存使用(以及算法复杂性),而跨步数组是表示当它们不连续放置时它们在内存中的位置。
    • 这就是维基百科的Stride of an array。但是,在 C 或 C++ 等语言中,这并不是一个有趣的属性。它只是数组元素的大小。引用的语言是 PL/1。我不觉得该页面引人注目——尽管我没有必要的特定反数据来保证对其进行编辑。 answer by FireFly 也引用了这个页面。
    【解决方案3】:

    如果要对二维数组的子集进行操作,则需要知道数组的“步幅”。假设你有:

    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);
    

    【讨论】:

      【解决方案4】:

      在高度优化的代码中,一种相当常见的技术是将填充插入到数组中。这意味着第 N 个逻辑元素不再位于偏移量N*sizeof(T)。这可能是一种优化的原因是某些缓存是关联性受限的。这意味着他们不能为某些对 i,j 缓存 array[i] 和 array[j]。如果在密集数组上运行的算法会使用许多这样的对,则插入一些填充可能会减少这种情况。

      发生这种情况的常见情况是在图像处理中。图像通常具有 512 字节或另一个“二进制整数”的线宽,并且许多图像处理例程使用像素的 3x3 邻域。结果,您可以在某些缓存体系结构上获得相当多的缓存驱逐。通过在每行的末尾插入“奇怪”数量的假像素(例如 3 个),您可以更改“步幅”并且相邻行之间的缓存干扰更少。

      这是非常特定于 CPU 的,因此这里没有一般建议。

      【讨论】:

        【解决方案5】:

        我在这里添加另一个答案,因为我没有找到任何令人满意的现有答案。

        Wikipedia 解释了 stride 的概念,并写道“stride 不能小于元素大小(这意味着元素重叠)但可以更大(表示元素之间有额外的空间) )”。

        但是,根据我发现的信息,跨步数组 完全可以做到这一点:通过允许跨度为零或负数来节省内存。

        跨步数组

        Compiling APL to JavaScript 将跨步数组解释为一种同时使用数据和跨度来表示多维数组的方法,这与假定隐式跨度为 1 的数组的典型“矩形”表示不同。它允许正跨度、负跨度和零跨度。为什么?它允许许多操作只改变步幅和形状,而不是底层数据,从而允许有效地操作大型数组。

        在处理大量数据时,这种跨步表示的优势变得显而易见。 transpose (⍉⍵)、reverse (⌽⍵) 或 drop (⍺↓⍵) 等函数可以重用数据数组,并且只需要为其结果赋予新的形状、步幅和偏移量。重新整形的标量,例如1000000⍴0,只能占用一定的内存,利用strides可以为0的事实。

        我还没有弄清楚这些操作将如何实现为跨步和形状上的操作,但很容易看出,仅更改这些而不是基础数据在计算方面会便宜得多。但是,值得记住的是,跨步表示可能会对缓存局部性产生负面影响,因此根据用例,使用常规矩形数组可能会更好。

        【讨论】:

          【解决方案6】:

          可能性一:Stride 描述一个缓冲数组来读取一个优化的数组

          当你使用一种方法来 store multidimensional arrays in linear storage。步幅描述了缓冲区每个维度的大小,这将帮助您读取该数组。图片取自Nd4j (More info about Stride)

          可能性2(较低级别):步幅是数组的连续成员之间的距离

          这意味着索引为 0 和 1 的项目的地址在内存中不会是连续的,除非您使用单位步长。值越大,项目在内存中的距离就越远。

          这在低级别很有用(字长优化、重叠数组、缓存优化)。参考wikipedia

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-02
            • 2013-03-23
            • 1970-01-01
            • 1970-01-01
            • 2018-10-07
            相关资源
            最近更新 更多