処理時間の短縮
注意点
本節での処理時間計測は、Releaseビルドのデバッグなしで実行しています。
現状の把握
まずは現状のソースファイルですが、以下のようになっているかと思います。
// 処理時間計測開始 long cntStart = 0; QueryPerformanceCounter(ref cntStart); unsafe { // 出力ビットマップの領域確保 this.grayscale = new Bitmap(this.bitmap.Width, this.bitmap.Height, PixelFormat.Format8bppIndexed); // 入出力ビットマップをシステムメモリにロック BitmapData dataRgb = this.bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); BitmapData dataGray = this.grayscale.LockBits( new Rectangle(0, 0, this.grayscale.Width, this.grayscale.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); byte* datas24bppRgb = (byte*)dataRgb.Scan0; byte* datas8bppGray = (byte*)dataGray.Scan0; // 4バイト境界の考慮(入力) int lineIn = this.bitmap.Width * 3; int lineInNew = (lineIn % 4 != 0) ? ((lineIn / 4 + 1) * 4) : lineIn; int lineInDiff = lineInNew - lineIn; int offsetIn = 0; // 4バイト境界の考慮(出力) int lineOut = this.bitmap.Width; int lineOutNew = (lineOut % 4 != 0) ? ((lineOut / 4 + 1) * 4) : lineOut; int lineOutDiff = lineOutNew - lineOut; int offsetOut = 0; // ポインタを介して、ピクセル毎にグレースケール化 for (int y = 0; y < this.bitmap.Height; y++) { for (int x = 0; x < this.bitmap.Width; x++) { // 4バイト境界の考慮 datas8bppGray[y * this.bitmap.Width + x + offsetOut] = (byte)( ((int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn]) + (int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn + 1]) + (int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn + 2])) / 3); } // 4バイト境界の考慮 offsetIn += lineInDiff; offsetOut += lineOutDiff; } // グレースケールのパレット情報の設定 ColorPalette palette = this.grayscale.Palette; for (int i = 0; i < palette.Entries.Length; ++i) { Color clr = Color.FromArgb(i, i, i); palette.Entries[i] = clr; } this.grayscale.Palette = palette; // 入出力ビットマップのロックを解除 this.bitmap.UnlockBits(dataRgb); this.grayscale.UnlockBits(dataGray); } // 処理時間計測終了 long cntEnd = 0; QueryPerformanceCounter(ref cntEnd); long frq = 0; QueryPerformanceFrequency(ref frq); double time = ((double)cntEnd - (double)cntStart) / (double)frq; time *= 1000; labelTAT.Text = time.ToString("#0.0000") + "ms";
この状態で、1280×1024の画像をグレースケール化すると、(私の環境では)概ね1156ms程度かかります。計測点を、ネストしているfor文の前後に移動しても、さほど違いが見られなかったため、このネストしているfor文の処理時間が短縮できれば、全体の処理速度も速くなるはずです。
// ポインタを介して、ピクセル毎にグレースケール化 for (int y = 0; y < this.bitmap.Height; y++) { for (int x = 0; x < this.bitmap.Width; x++) { // 4バイト境界の考慮 datas8bppGray[y * this.bitmap.Width + x + offsetOut] = (byte)( ((int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn]) + (int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn + 1]) + (int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn + 2])) / 3); } // 4バイト境界の考慮 offsetIn += lineInDiff; offsetOut += lineOutDiff; }
記述こそ単純ですが、無駄な計算を何度も行っており、非常に遅いです。これから順にチューンアップしていきます。
まず、インデックスの算出を何度も繰り返しているため、その部分に手を入れます。
// ポインタを介して、ピクセル毎にグレースケール化 for (int y = 0; y < this.bitmap.Height; y++) { for (int x = 0; x < this.bitmap.Width; x++) { // 4バイト境界の考慮 int indexIn = y * this.bitmap.Width * 3 + x * 3 + offsetIn; int indexOut = y * this.bitmap.Width + x + offsetOut; datas8bppGray[indexOut] = (byte)( ((int)(datas24bppRgb[indexIn]) + (int)(datas24bppRgb[indexIn + 1]) + (int)(datas24bppRgb[indexIn + 2])) / 3); } // 4バイト境界の考慮 offsetIn += lineInDiff; offsetOut += lineOutDiff; }
これで、400msほど高速化できました。
次に、インデックスのY成分を算出している部分は、Xのループの外に出せますので、修正します。
// ポインタを介して、ピクセル毎にグレースケール化 for (int y = 0; y < this.bitmap.Height; y++) { int indexYOut = y * this.bitmap.Width; int indexYIn = indexYOut * 3; for (int x = 0; x < this.bitmap.Width; x++) { // 4バイト境界の考慮 int indexIn = indexYIn + x * 3 + offsetIn; int indexOut = indexYOut + x + offsetOut; datas8bppGray[indexOut] = (byte)( ((int)(datas24bppRgb[indexIn]) + (int)(datas24bppRgb[indexIn + 1]) + (int)(datas24bppRgb[indexIn + 2])) / 3); } // 4バイト境界の考慮 offsetIn += lineInDiff; offsetOut += lineOutDiff; }
これで、300msほど高速化できました。
インデックス記述ではなく、ポインタの参照外しに修正します。
// ポインタを介して、ピクセル毎にグレースケール化 for (int y = 0; y < this.bitmap.Height; y++) { int indexYOut = y * this.bitmap.Width; int indexYIn = indexYOut * 3; for (int x = 0; x < this.bitmap.Width; x++) { // 4バイト境界の考慮 int indexIn = indexYIn + x * 3 + offsetIn; int indexOut = indexYOut + x + offsetOut; *(datas8bppGray + indexOut) = (byte)( ((int)(*(datas24bppRgb + indexIn)) + (int)(*(datas24bppRgb + indexIn + 1)) + (int)(*(datas24bppRgb + indexIn + 2))) / 3); } // 4バイト境界の考慮 offsetIn += lineInDiff; offsetOut += lineOutDiff; }
若干ではありますが高速化しているようです。
最終的には、以下となりました。
int width = this.bitmap.Width; int height = this.bitmap.Height; byte* currentData = datas24bppRgb - 1; for (int y = 0; y < height; ++y) { int widthY = y * width; for (int x = 0; x < width; ++x) { *(datas8bppGray + widthY + x + offsetOut) = (byte)(((int)*++currentData + (int)*++currentData + (int)*++currentData) / 3); } currentData += lineInDiff; offsetOut += lineOutDiff; }