有没有比简单地使用倒置坐标进行嵌套循环更快的方法将大位图旋转 90 度或 270 度?
位图为 8bpp,通常为 2048x2400x8bpp
目前我通过简单地复制参数反转来做到这一点,大致(伪代码:
for x = 0 to 2048-1
for y = 0 to 2048-1
dest[x][y]=src[y][x];
(实际上我用指针来做,速度更快,但幅度大致相同)
GDI 对于大图像非常慢,并且纹理(GF7 卡)的 GPU 加载/存储时间与当前 CPU 时间相同。
任何提示,指针?就地算法甚至会更好,但速度比就地更重要。
目标是德尔福,但它更像是一个算法问题。SSE(2) 向量化没问题,这对我来说在汇编程序中编码已经足够大了
跟进尼尔斯的回答
- 图像 2048x2700 -> 2700x2048
- 编译器 Turbo Explorer 2006 已开启优化。
- Windows:电源方案设置为“始终开启”。(重要!!!!)
- 机器:Core2 6600 (2.4 GHz)
使用旧程序的时间:32 毫秒(第 1 步)
步长为 8 的时间:12ms
步长为 16 的时间:10 毫秒
步长 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>
所以 8x8 需要 9 个寄存器,但 32 位 SSE 只有 8 个。无论如何,这是夏天的事情:-)
请注意,指针的事情是我出于本能而做的,但它实际上可能有一些东西,如果你的尺寸没有硬编码,编译器就不能把 mul 变成一个班次。虽然现在 muls an sich 很便宜,但它们也会产生更多的注册压力 afaik。
代码(通过从“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,i)
ps2:=@ptarget[stepsize-1-i]; // (maxx-i,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,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;
if (resty>0) and (restx>0) then
begin
// another loop less, since only one block
psource:=source.GetImagePointer(source.ImageWidth-restx,source.ImageHeight-resty); // gets pointer to pixel x,y
ptarget:=Target.GetImagePointer(0,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 中使用
更新 4
在 2017 年,我在8x8 立方体的 8bpp 图像的汇编器版本上做了一些工作,以及关于 shuffle 瓶颈的相关SO 问题,Peter Cordes 慷慨地帮助了我。这段代码仍然有一个错过的机会,并且仍然需要另一个循环平铺级别再次将多个 8x8 块迭代聚合成更大的伪迭代,例如 64x64。现在又是整行了,这很浪费。