如何有效地转置矩阵?是否有这样的库,或者你会使用什么算法?
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上嵌入了不支持汇编的工具链)
答案 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++;
}
}
由于问题的性质,我不知道如何避免缓存局部性问题。