ピクセル変更を行う必要があることを考えると、全体的な問題を見てみましょう。30000x4000 ピクセルの中間イメージは、8 ビット グレーの場合は 120M のイメージ データ、16 ビットの場合は 240M のイメージ データです。このようにデータを見ているのであれば、「30分は妥当か?」と問う必要があります。90 度の回転を行うには、メモリに関して最悪のケースの問題が発生します。1 つの行を埋めるために、1 つの列のすべてのピクセルに触れています。行単位で作業する場合、少なくともメモリ フットプリントが 2 倍になることはありません。
つまり、120M のピクセルは、120M の読み取りと 120M の書き込み、または 240M のデータ アクセスを行っていることを意味します。これは、1 秒あたり約 66,667 ピクセルを処理していることを意味します。これは遅すぎると思います。毎秒少なくとも50 万ピクセル、おそらくそれ以上を処理する必要があると思います。
これが私だったら、プロファイリング ツールを実行して、ボトルネックがどこにあるかを確認し、それらを取り除きます。
正確な構造を知らずに推測する必要がなければ、次のことを行います。
ソース イメージに 1 つの連続したメモリ ブロックを使用しようとする
次のような回転関数を見たいと思います:
void RotateColumn(int column, char *sourceImage, int bytesPerRow, int bytesPerPixel, int height, char *destRow)
{
char *src = sourceImage + (bytesPerPixel * column);
if (bytesPerPixel == 1) {
for (int y=0; y < height; y++) {
*destRow++ = *src;
src += bytesPerRow;
}
}
else if (bytesPerPixel == 2) {
for (int y=0; y < height; y++) {
*destRow++ = *src;
*destRow++ = *(src + 1);
src += bytesPerRow;
// although I doubt it would be faster, you could try this:
// *destRow++ = *src++;
// *destRow++ = *src;
// src += bytesPerRow - 1;
}
}
else { /* error out */ }
}
ループの内側はおそらく8命令になると思います。2GHz プロセッサ (公称では命令ごとに 4 サイクルとしましょう。これは単なる推測です) では、1 秒間に 6 億 2,500 万ピクセルを回転できるはずです。だいたい。
連続できない場合は、一度に複数の dest スキャンラインで作業してください。
ソース画像がブロックに分割されている場合、またはメモリのスキャンライン抽象化がある場合、ソース画像からスキャンラインを取得し、たとえば数十列を一度に回転させて、dest スキャンラインのバッファーに入れます。
スキャンラインに抽象的にアクセスするためのメカニズムがあり、スキャンラインを取得して解放し、書き込むことができると仮定しましょう。
次に、コードが次のようになるため、一度に処理するソース列の数を計算します。
void RotateNColumns(Pixels &source, Pixels &dest, int startColumn, int nCols)
{
PixelRow &rows[nRows];
for (int i=0; i < nCols; i++)
rows[i] = dest.AcquireRow(i + startColumn);
for (int y=0; y < source.Height(); y++) {
PixelRow &srcRow = source.AcquireRow();
for (int i=0; i < nCols; i++) {
// CopyPixel(int srcX, PixelRow &destRow, int dstX, int nPixels);
sourceRow.CopyPixel(startColumn + i, rows[i], y, 1);
}
source.ReleaseRow(srcRow);
}
for (int i=0; i < nCols; i++)
dest.ReleaseAndWrite(rows[i]);
}
この場合、スキャンラインの大きなブロックでソース ピクセルをバッファリングする場合、必ずしもヒープを断片化するわけではなく、デコードされた行をディスクにフラッシュすることもできます。一度に n 列を処理すると、メモリの局所性が n 倍向上するはずです。次に、キャッシングがどれだけ高価かという問題になります。
この問題は並列処理で解決できますか?
正直なところ、あなたの問題はCPUバウンドではなく、IOバウンドであるべきだと思います。あなたのデコード時間が支配的だと思いますが、そうではないふりをしましょう。
このように考えてみてください。一度にソース イメージの行全体を読み取ると、そのデコードされた行を、宛先イメージの適切な列に書き込むスレッドに投げることができます。OnRowDecoded(byte *row, int y, int width, int bytesPerPixel); のようなメソッドを持つようにデコーダを作成します。そして、デコード中に回転しています。OnRowDecoded() は情報をまとめて、dest イメージを所有するスレッドに渡し、デコードされた行全体を正しい dest 列に書き込みます。そのスレッドは、メイン スレッドが次の行のデコードでビジー状態である間に、すべての dest への書き込みを行います。ワーカー スレッドが最初に終了する可能性がありますが、そうでない場合もあります。
宛先への SetPixel() をスレッドセーフにする必要がありますが、それ以外に、これをシリアルタスクにする理由はありません。実際、ソース画像がバンドまたはタイルに分割される TIFF 機能を使用している場合、それらを並行してデコードできますし、またデコードする必要があります。