位图为8bpp,通常为2048 * 2400 * 8bpp
目前我简单地用参数反转复制,大致(伪代码:
for x = 0 to 2048-1 for y = 0 to 2048-1 dest[x][y]=src[y][x];
(实际上,我用指针做了一些更快的速度,但是大致相同)
GDI与大图像相当缓慢,纹理(GF7卡)的GPU加载/存储时间与当前cpu时间大致相同。
任何提示,指针?就地算法甚至会更好,但速度比原位更重要。
目标是德尔福,但它更是一个算法问题。 SSE(2)向量化没有问题,这对我来说是一个足够大的问题,在汇编程序中编码
跟随尼尔斯的回答
>图像2048×2700 – > 2700×2048
>编译器Turbo Explorer 2006与优化。
> Windows:电源方案设置为“始终开启”。 (重要!!!!)
>机器:Core2 6600(2.4 GHz)
时间与旧例程:32ms(步骤1)
时间与步骤8:12ms
时间与步骤16:10ms
时间与步骤32:9ms
同时我还在Athlon 64 X2(5200 iirc)上进行了测试,其速度略高于四分之一(80到19毫秒)。
谢谢你的加速。也许在夏季,我会用SSE(2)版本折磨自己。但是我已经考虑过如何解决这个问题,我想我将用完SSE2寄存器来直接实现:
for n:=0 to 7 do begin load r0,<source+n*rowsize> shift byte from r0 into r1 shift byte from r0 into r2 .. shift byte from r0 into r8 end; store r1,<target> store r2,<target+1*<rowsize> .. store r8,<target+7*<rowsize>
所以8×8需要9个寄存器,但32位SSE只有8个。无论如何,这是夏天的一天:-)
注意,指针的东西是我本能的本能,但是实际上可能有一些东西,如果你的维度没有硬编码,那么编译器就不能把这个变成一个转变。虽然现在这样一个人很便宜,但是他们也会产生更多的登记压力。
代码(通过从“naieve”rotate1实现中减去结果来验证):
const stepsize = 32; procedure rotatealign(Source: tbw8image; Target:tbw8image); var stepsx,stepsy,restx,resty : Integer; RowPitchSource,RowPitchTarget : Integer; pSource,pTarget,ps1,ps2 : pchar; x,y,i,j: integer; rpstep : integer; begin RowPitchSource := source.RowPitch; // bytes to jump to next line. Can be negative (includes alignment) RowPitchTarget := target.RowPitch; rpstep:=RowPitchTarget*stepsize; stepsx:=source.ImageWidth div stepsize; stepsy:=source.ImageHeight div stepsize; // check if mod 16=0 here for both dimensions,if so -> SSE2. for y := 0 to stepsy - 1 do begin psource:=source.GetImagePointer(0,y*stepsize); // gets pointer to pixel x,y ptarget:=Target.GetImagePointer(target.imagewidth-(y+1)*stepsize,0); for x := 0 to stepsx - 1 do begin for i := 0 to stepsize - 1 do begin ps1:=@psource[rowpitchsource*i]; // ( 0,i) ps2:=@ptarget[stepsize-1-i]; // (maxx-i,0); for j := 0 to stepsize - 1 do begin ps2[0]:=ps1[j]; inc(ps2,RowPitchTarget); end; end; inc(psource,stepsize); inc(ptarget,rpstep); end; end; // 3 more areas to do,with dimensions // - stepsy*stepsize * restx // right most column of restx width // - stepsx*stepsize * resty // bottom row with resty height // - restx*resty // bottom-right rectangle. restx:=source.ImageWidth mod stepsize; // typically zero because width is // typically 1024 or 2048 resty:=source.Imageheight mod stepsize; if restx>0 then begin // one loop less,since we know this fits in one line of "blocks" psource:=source.GetImagePointer(source.ImageWidth-restx,0); // gets pointer to pixel x,y ptarget:=Target.GetImagePointer(Target.imagewidth-stepsize,Target.imageheight-restx); for y := 0 to stepsy - 1 do begin for i := 0 to stepsize - 1 do begin ps1:=@psource[rowpitchsource*i]; // ( 0,0); for j := 0 to restx - 1 do begin ps2[0]:=ps1[j]; inc(ps2,RowPitchTarget); end; end; inc(psource,stepsize*RowPitchSource); dec(ptarget,stepsize); end; end; if resty>0 then begin // one loop less,since we know this fits in one line of "blocks" psource:=source.GetImagePointer(0,source.ImageHeight-resty); // gets pointer to pixel x,y ptarget:=Target.GetImagePointer(0,0); for x := 0 to stepsx - 1 do begin for i := 0 to resty- 1 do begin ps1:=@psource[rowpitchsource*i]; // ( 0,i) ps2:=@ptarget[resty-1-i]; // (maxx-i,stepsize); inc(ptarget,rpstep); end; end; if (resty>0) and (restx>0) then begin // another loop less,since only one block psource:=source.GetImagePointer(source.ImageWidth-restx,target.ImageHeight-restx); for i := 0 to resty- 1 do begin ps1:=@psource[rowpitchsource*i]; // ( 0,i) ps2:=@ptarget[resty-1-i]; // (maxx-i,0); for j := 0 to restx - 1 do begin ps2[0]:=ps1[j]; inc(ps2,RowPitchTarget); end; end; end; end;
更新2泛型
我试图在Delphi XE中将此代码更新为泛型版本。我因为QC 99703失败了,而且论坛的人已经确认也存在于XE2中。请投票:-)
更新3泛型
现在在XE10工作
解决方法
您的简单循环大部分时间都用于缓存未命中。这样会发生,因为你在一个紧张的循环中碰到很多不同的地方的数据。更糟糕的是,你的记忆位置恰好是两分之一的力量。这是缓存性能最差的大小。
如果改善了内存访问的位置,则可以改进此旋转算法。
这样做的一个简单方法是使用与您完整位图相同的代码来旋转其自己的每个8×8像素块,并将另一个循环将图像旋转分成8×8像素。
例如。这样的东西(没有检查,对C代码很抱歉,我的Delphi技能不是最新的):
// this is the outer-loop that breaks your image rotation // into chunks of 8x8 pixels each: for (int block_x = 0; block_x < 2048; block_x+=8) { for (int block_y = 0; blocky_y < 2048; block_y+=8) { // this is the inner-loop that processes a block // of 8x8 pixels. for (int x= 0; x<8; x++) for (int y=0; y<8; y++) dest[x+block_x][y+block_y] = src[y+block_y][x+block_x] } }
还有其他方法。您可以处理Hilbert-Order或Morton-Order中的数据。这在理论上甚至会更快一点,但代码将会更加复杂。
Btw – 既然你提到SSE是你的选择。请注意,您可以旋转SSE寄存器中的8×8字节块。这是一个有点棘手的工作,但看看SSE矩阵转置代码应该让你开始,因为它是一样的事情。
编辑:
刚刚检查:
具有8×8像素的块大小,代码运行ca.在我的机器上快5倍。 16×16的块大小可以快10倍。
好像想尝试不同的块大小是好主意。
这是我使用的(非常简单的)测试程序:
#include <stdio.h> #include <windows.h> char temp1[2048*2048]; char temp2[2048*2048]; void rotate1 (void) { int x,y; for (y=0; y<2048; y++) for (x=0; x<2048; x++) temp2[2048*y+x] = temp1[2048*x+y]; } void rotate2 (void) { int x,y; int bx,by; for (by=0; by<2048; by+=8) for (bx=0; bx<2048; bx+=8) for (y=0; y<8; y++) for (x=0; x<8; x++) temp2[2048*(y+by)+x+bx] = temp1[2048*(x+bx)+y+by]; } void rotate3 (void) { int x,by; for (by=0; by<2048; by+=16) for (bx=0; bx<2048; bx+=16) for (y=0; y<16; y++) for (x=0; x<16; x++) temp2[2048*(y+by)+x+bx] = temp1[2048*(x+bx)+y+by]; } int main (int argc,char **args) { int i,t1; t1 = GetTickCount(); for (i=0; i<20; i++) rotate1(); printf ("%d\n",GetTickCount()-t1); t1 = GetTickCount(); for (i=0; i<20; i++) rotate2(); printf ("%d\n",GetTickCount()-t1); t1 = GetTickCount(); for (i=0; i<20; i++) rotate3(); printf ("%d\n",GetTickCount()-t1); }