【问题标题】:Transpose a 2D array转置二维数组
【发布时间】:2010-11-29 23:54:36
【问题描述】:

如何有效地转置矩阵?有没有这方面的库,或者你会使用什么算法?

例如:

short src[W*H] = {
  {1,2,3},
  {4,5,6}
};
short dest[W*H];


rotate_90_clockwise(dest,src,W,H); //<-- magic in here, no need for in-place

//dest is now:

{
  {4, 1},
  {5, 2},
  {6, 3}
};

(在我的具体情况下,它的 src 数组是原始图像数据,目标是帧缓冲区,我嵌入在 ARM 上的不支持汇编的工具链上)

【问题讨论】:

  • 那是作业吗? ;-)
  • 这实际上不是一个通常的矩阵转置 - 转置映射 (row, col)(col, row)
  • 它也有助于了解您嵌入的内容。例如,可以访问 GPU 的 smoeth 可以轻松地使用他们的点积运算。

标签: c performance algorithm embedded matrix


【解决方案1】:

这里最有效的解决方案是在将数据从 RAM 复制到帧缓冲区时旋转数据。在 RAM 中旋转源,然后将结果复制到帧缓冲区,充其量是复制和旋转版本速度的一半。所以,问题是,顺序读取和随机写入还是随机读取和顺序写入更有效。在代码中,这将是以下之间的选择:

// read sequential
src = { image data }
dest = framebuffer
for (y = 0 ; y < H ; ++y)
{
   for (x = 0 ; x < W ; ++x)
   {
     pixel = *src++
     dest [y,x] = pixel
   }
}

或:

// write sequential
src = { image data }
dest = framebuffer
for (x = 0 ; x < W ; ++x)
{
   for (y = 0 ; y < H ; ++y)
   {
     pixel = src [x,y]
     *dest++ = pixel
   }
}

这个问题的答案只能通过分析代码来确定。

现在,您可能有一个 GPU,在这种情况下,它肯定有能力进行旋转,并且在将图像blitting 到屏幕时让 GPU 进行旋转会更有效率。

【讨论】:

  • 这是我自己的起点,但我一直在尝试同时在多条扫描线上使用“光标”,假设缓存未命中率会减少。
【解决方案2】:

在某些情况下,有用于此的库。而且,值得注意的是,您可以使用向量化数据(例如,128 位向量中的四个 32 位元素,但这也适用于 32 位寄存器中的四个 8 位字节)比单个-元素访问。

对于转置,标准的想法是使用“shuffle”指令,它允许您以任意顺序从两个现有向量中创建一个新的数据向量。您使用输入数组的 4x4 块。所以,一开始,你有:

v0 = 1 2 3 4
v1 = 5 6 7 8
v2 = 9 A B C
v3 = D E F 0

然后,对前两个向量应用 shuffle 指令(交织它们的奇数元素,A0B0 C0D0 -> ABCD,并交织它们的偶数元素,0A0B 0C0D -> ABCD),然后对最后两个向量应用洗牌指令,以创建一个新集合每个 2x2 块转置的向量:

1 5 3 7
2 6 4 8
9 D B F
A E C 0

最后,你对奇数对和偶数对应用洗牌指令(结合它们的第一对元素 AB00 CD00 -> ABCD 和它们的最后一对元素 00AB 00CD -> ABCD),得到:

1 5 9 D
2 6 A E
3 7 B F
4 8 C 0

还有 16 个元素转换成 8 条指令!

现在,对于 32 位寄存器中的 8 位字节,ARM 没有完全的 shuffle 指令,但是您可以通过移位和 SEL(选择)指令来合成您需要的内容,并且您可以使用第二组 shuffle在一条指令中使用 PKHBT(打包半字底部顶部)和 PKHTB(打包半字顶部底部)指令。

最后,如果您使用具有 NEON 向量化的大型 ARM 处理器,您可以在 16x16 块上使用 16 元素向量执行类似的操作。

【讨论】:

  • 这是一个适当的矩阵转置(第 1 行变为第 1 列),问题中给出的示例是矩阵旋转(第 1 行变为第 2 列)。
【解决方案3】:

只是一个简单的复制到临时和复制回,在你去的时候转置,使用指针步进来避免地址计算中的乘法,并且内循环展开:

char temp[W*H];
char* ptemp = temp;
memcpy(temp, array, sizeof(char)*W*H);
for (i = 0; i < H; i++){
    char* parray = &array[i];
    for (j = 0; j+8 <= W; j += 8, ptemp += 8){
        *parray = ptemp[0]; parray += H;
        *parray = ptemp[1]; parray += H;
        *parray = ptemp[2]; parray += H;
        *parray = ptemp[3]; parray += H;
        *parray = ptemp[4]; parray += H;
        *parray = ptemp[5]; parray += H;
        *parray = ptemp[6]; parray += H;
        *parray = ptemp[7]; parray += H;
    }
    for (; j < W; j++, parray += H){
        *parray = *ptemp++;
    }
}

由于问题的性质,我不知道如何避免缓存局部性问题。

【讨论】:

    【解决方案4】:

    维基百科在原地矩阵转置上有一个entire article。对于非方阵,这是一个不平凡、相当有趣的问题(即使用少于 O(N x M) 的内存)。这篇文章有很多关于算法的论文的链接,以及一些源代码。

    但请注意 - 正如我在对您的问题的评论中所说,您的演示不是的标准转置,所有算法都将为此编写。

    (标准转置函数将为您的示例数据提供此结果:)

    {
      {1, 4},
      {2, 5},
      {3, 6}
    };
    

    如果您只是为了在屏幕上显示图像,最好在将图像复制到后台缓冲区时进行转置,而不是在原地转置然后进行位图传输。

    【讨论】:

      【解决方案5】:
      • 如果矩阵是正方形或者如果您不是在寻找就地转置,这真的很容易:

      基本上,您在行上进行迭代并将每个项目与匹配的列项目交换。您可以通过交换行和列索引来获得匹配项。当您处理完所有列后,转置就完成了。您也可以反过来对列进行迭代。

      如果您想提高性能,可以将整行复制到临时数组中,将完整匹配列复制到另一个数组中,然后将它们复制回来。如果您使用 memcopy 进行涉及最内部元素的传输,它应该会稍微快一些(即使此策略涉及更多变量赋值)。

      • 如果矩阵不是正方形的(如您的示例中所示),则在原地执行它真的很棘手。由于转置不会改变内存需求,它看起来仍然可以就地执行,但如果您不小心这样做,您最终会覆盖另一行或另一列的元素。

      如果内存不是瓶颈,我建议使用临时矩阵。它真的很容易,而且可能会更快。

      • 最好的方法是根本不转置,而只是在某处设置一个标志,说明您是先访问数据还是先访问数据。在大多数情况下,需要转置的算法可以被重写以访问未转置的矩阵,就好像它是一样。要实现这一点,您只需重写一些基本操作,例如矩阵乘积,以接受具有一个方向或另一个方向的矩阵。

      但在某些情况下,我理解这是不可能的,通常是在准备数据以供某些现有硬件或库访问时。

      【讨论】:

        【解决方案6】:

        在 O(1) 中有效的一个非常简单的解决方案是为矩阵保存一个额外的布尔值,说明它是否“转置”。 然后根据这个布尔值(row/col or col/row)访问数组。

        当然,它会阻碍您的缓存利用率。

        因此,如果您有许多转置操作,并且很少有“完整遍历”(顺便说一句,也可能根据布尔值重新排序),那么这是您的最佳选择。

        【讨论】:

        • 我会赞成这是一个非常好的开箱即用的解决方案。如果您通过 API 而不是直接访问矩阵,您可以很容易地拥有一个带有转置标志和实际数据的结构,并使用转置标志返回宽度和高度,并将它们交换为 getter 和 setter。
        • 另外,如果你想避免人们谈论的所有缓存问题,只需将正常副本和转置副本同时保存在内存中(setter API 可以确保它们永远不会失步) .对于这种特定情况可能没有好处(因为它是嵌入式的),但对于常规系统来说可能是值得的。
        • 它跳出框框思考,但它不会旋转横向图像以将其显示在纵向内存屏幕上。
        • 这只是推迟了问题,但当然这可能正是您所需要的!
        • 威尔,这真的取决于你想用你的矩阵做什么。在您的情况下,您需要将其传递给屏幕(我对图像/在屏幕上放置东西不是很熟悉),所以这种方法在这里可能不是灵丹妙药。在其他情况下,您需要做的是乘以矩阵,或者在转置时访问它(从中读取)。或者也许找到一个子矩阵等。对于上面的例子,你确实需要转置矩阵(概念上)。您可以使用上述方法完全避免“实际转置”它。
        猜你喜欢
        • 1970-01-01
        • 2014-06-21
        • 2014-11-29
        • 2013-06-29
        • 2020-09-20
        • 2019-01-23
        • 2010-10-13
        相关资源
        最近更新 更多