5

私はいくつかの安全でないビットマップ操作を行ってきましたが、ポインターを増やす回数を減らすと、パフォーマンスが大幅に向上する可能性があることがわかりました。なぜそうなのかはわかりませんが、ループ内でより多くのビット単位の演算を実行する場合でも、ポインターに対して実行する反復回数を減らす方がよいでしょう。

したがって、たとえば、UInt32で32ビットピクセルを反復する代わりに、UInt64で2ピクセルを反復し、1サイクルで2回の操作を実行します。

以下は、2つのピクセルを読み取り、それらを変更することによってそれを行います(もちろん、奇数幅の画像では失敗しますが、テスト用です)。

    private void removeBlueWithTwoPixelIteration()
    {
        // think of a big image with data
        Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        TimeSpan startTime, endTime;

        unsafe {

            UInt64 doublePixel;
            UInt32 pixel1;
            UInt32 pixel2;

            const int readSize = sizeof(UInt64);
            const UInt64 rightHalf = UInt32.MaxValue;

            PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue();

            BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
            byte* image = (byte*)bd.Scan0.ToPointer();

            startTime = TimeSpan.FromSeconds(pf.NextValue());

            for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride)
            {
                for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
                {
                    doublePixel = *((UInt64*)pointer);
                    pixel1 = (UInt32)(doublePixel >> (readSize * 8 / 2)) >> 8; // loose last 8 bits (Blue color)
                    pixel2 = (UInt32)(doublePixel & rightHalf) >> 8; // loose last 8 bits (Blue color)
                    *((UInt32*)pointer) = pixel1 << 8; // putback but shift so A R G get back to original positions
                    *((UInt32*)pointer + 1) = pixel2 << 8; // putback but shift so A R G get back to original positions
                }
            }

            endTime = TimeSpan.FromSeconds(pf.NextValue());

            bmp.UnlockBits(bd);
            bmp.Dispose();

        }

        MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString());

    }

次のコードはピクセルごとに実行し、前のコードよりも約70%遅くなります。

    private void removeBlueWithSinglePixelIteration()
    {
        // think of a big image with data
        Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        TimeSpan startTime, endTime;

        unsafe
        {

            UInt32 singlePixel;

            const int readSize = sizeof(UInt32);

            PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue();

            BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
            byte* image = (byte*)bd.Scan0.ToPointer();

            startTime = TimeSpan.FromSeconds(pf.NextValue());

            for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride)
            {
                for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
                {
                    singlePixel = *((UInt32*)pointer) >> 8; // loose B
                    *((UInt32*)pointer) = singlePixel << 8; // adjust A R G back
                }
            }

            endTime = TimeSpan.FromSeconds(pf.NextValue());

            bmp.UnlockBits(bd);
            bmp.Dispose();

        }

        MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString());
    }

誰かがポインタをインクリメントすることが、いくつかのビット演算を実行するよりもコストのかかる操作である理由を明確にできますか?

.NET4フレームワークを使用しています。

このようなことがC++にも当てはまりますか?

NB。32ビットと64ビットの2つの方法の比率は同じですが、どちらの方法も64ビットと32ビットで20%遅くなりますか?

編集:Porgesとarulが示唆しているように、これはメモリ読み取り数の減少と分岐オーバーヘッドが原因である可能性があります。

EDIT2:

いくつかのテストの後、メモリからの読み取りにかかる時間が短縮されたことが答えのようです。

このコードでは、画像の幅が5で割り切れると仮定すると、400%速くなります。

[StructLayout(LayoutKind.Sequential,Pack = 1)]
struct PixelContainer {
    public UInt32 pixel1;
    public UInt32 pixel2;
    public UInt32 pixel3;
    public UInt32 pixel4;
    public UInt32 pixel5;
}

次に、これを使用します。

            int readSize = sizeof(PixelContainer);

            // .....

            for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
            {
                multiPixel = *((PixelContainer*)pointer);
                multiPixel.pixel1 &= 0xFFFFFF00u;
                multiPixel.pixel2 &= 0xFFFFFF00u;
                multiPixel.pixel3 &= 0xFFFFFF00u;
                multiPixel.pixel4 &= 0xFFFFFF00u;
                multiPixel.pixel5 &= 0xFFFFFF00u;
                *((PixelContainer*)pointer) = multiPixel;
            }
4

2 に答える 2

5

これは、ループ展開と呼ばれる手法です。主なパフォーマンス上の利点は、分岐のオーバーヘッドを削減することから得られるはずです。

ちなみに、ビットマスクを使用すると、少しスピードアップできます。

*((UInt64 *)pointer) &= 0xFFFFFF00FFFFFF00ul;
于 2011-04-28T03:11:05.500 に答える
2

遅いのはポインタのインクリメントではなく、メモリからの読み取りです。32ビットユニットでは、2倍の読み取りを実行します。

64ビットバージョンで2回ではなく、1回書き込むと、再び高速になります。

于 2011-04-28T03:02:32.403 に答える