转置2D阵列

时间:2009-09-21 05:45:05

标签: c performance algorithm embedded matrix

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

E.g:

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上嵌入了不支持汇编的工具链)

6 个答案:

答案 0 :(得分:19)

在O(1)中工作的一个非常简单的解决方案是为矩阵保存一个额外的布尔值,说明它是否是“转置”的。 然后将根据此布尔值(行/列或列/行)访问数组。

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

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

答案 1 :(得分:10)

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

对于转置,标准的想法是使用“随机”指令,它允许您以任何顺序从两个现有向量中创建新的数据向量。您使用输入数组的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

最后,你将shuffle指令应用于奇数对和偶数对(组合它们的第一对元素,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没有准确的随机指令,但您可以使用移位和SEL(选择)指令合成所需的内容,并且可以使用第二组shuffle使用PKHBT(打包半字底部顶部)和PKHTB(打包半字顶部底部)指令执行一条指令。

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

答案 2 :(得分:4)

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

请注意 - 正如我在对您的问题的评论中所说,您的演示标准换位,所有算法都将为其编写。

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

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

如果您只是这样做以在屏幕上显示图像,那么在将图像复制到后台缓冲区时,最好只进行换位,而不是在原位移位然后进行blitting。

答案 3 :(得分:3)

  • 如果矩阵是方形的,或者如果你不是在寻找一个原位换位,那真的很容易:

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

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

  • 如果矩阵不是正方形(如在你的例子中那样),那么在原地进行它是非常棘手的。由于转置不会改变内存需求,它仍然可以在现场进行,但如果你不小心这样做,你最终会覆盖另一行或列的元素。

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

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

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

答案 4 :(得分: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,在这种情况下它肯定能够进行旋转,当将图像blit到屏幕时让GPU进行旋转会更有效率。

答案 5 :(得分:0)

只是一个简单的复制到临时和复制,转移,使用指针步进避免乘法地址计算,内循环展开:

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++;
    }
}

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