例如:
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上,使用不支持汇编的工具链。
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}
};
一种非常简单的O(1)解决方案是为矩阵保存一个额外的布尔值,表示矩阵是否为“转置”的状态。然后根据这个布尔值来访问数组(行/列或列/行)。
当然,这会影响你的缓存利用率。
所以如果你有很多转置操作,但是只有少量的“完整遍历”(顺便说一句,这些也可以根据布尔值进行重新排序),那么这是你最好的选择。
有一些库可以完成这个任务。值得注意的是,对于向量化数据(例如,在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
接着,您将洗牌指令应用于前两个向量(交错它们的奇数元素,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没有精确的洗牌指令,但您可以使用移位和SEL(选择)指令合成所需内容,并且第二组洗牌可以在一条指令中使用PKHBT(打包半字底部顶部)和PKHTB(打包半字顶部底部)指令执行。
最后,如果您正在使用具有NEON向量化的大型ARM处理器,则可以在16x16块上的16元素向量中执行类似于此的操作。
{
{1, 4},
{2, 5},
{3, 6}
};
如果您只是想在屏幕上显示图像,最好在将图像复制到后备缓冲区时进行转置,而不是就地转置然后再进行位块传送。
基本上,您可以在行上迭代并与匹配的列项交换每个项。通过交换行和列索引,您可以获得匹配项。当您处理完所有列时,转置就完成了。您也可以反过来迭代列。
如果您想提高性能,可以将一个完整的行复制到临时数组中,将完整的匹配列复制到另一个数组中,然后将它们复制回去。如果使用内部元素涉及传输,则应该稍微快一些(即使此策略涉及更多变量分配)。
如果内存不是瓶颈,我建议使用临时矩阵。这样做真的很容易,而且可能会更快。
但在某些情况下,我理解这是不可能的,通常是因为正在准备数据以供某些现有硬件或库访问。
// 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,那么它肯定具有旋转功能,并且在将图像传输到屏幕时让GPU执行旋转操作会更加高效。
只需简单地将数据复制到临时变量中,同时进行转置,并使用指针步进来避免地址计算中的乘法,内部循环展开:
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++;
}
}
由于问题的本质,我不知道如何避免缓存局部性问题。
(行,列)
映射到(列,行)
。 - caf