逆座標でネストされたループを単純に実行するよりも、大きなビットマップを 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 時間と同じ大きさです。
ヒント、指針はありますか?インプレース アルゴリズムはさらに優れていますが、速度はインプレースよりも重要です。
ターゲットは Delphi ですが、それはアルゴリズムの問題です。SSE(2) のベクトル化は問題ありません。アセンブラーでコーディングするには十分な問題です。
ニルスの答えをフォローアップ
- 画像 2048x2700 -> 2700x2048
- 最適化をオンにしたコンパイラ Turbo Explorer 2006。
- Windows: 電源設定が「常時オン」に設定されています。(重要!!!! )
- マシン: Core2 6600 (2.4 GHz)
古いルーチンの時間: 32ms (ステップ 1)
ステップサイズ 8 の時間: 12ms
ステップサイズ 16 の時間: 10ms
ステップサイズ 32+ の時間: 9ms
一方、Athlon 64 X2 (5200+ iirc) でもテストを行いましたが、速度は 4 倍をわずかに上回りました (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 をシフトに変換できません。最近ではマルチは安価ですが、より多くの音域のプレッシャーも発生します。
コード (「素朴な」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 にも存在することを既に確認しています。それに投票してください:-)
Update 3 Generics Works in XE10
更新 4
2017 年に、8bpp 画像のみの 8x8 キューブのアセンブラー バージョンと、Peter Cordes が寛大に私を助けてくれたシャッフルのボトルネックに関する関連するSO の質問にいくつかの作業を行いました。このコードはまだ機会を逃しており、複数の 8x8 ブロックの繰り返しを 64x64 のような疑似大きなものに集約するために、別のループタイリング レベルが再度必要です。今はまた行全体であり、それは無駄です。