Frage

Ich habe einige unsichere Bitmap -Operationen durchgeführt und herausgefunden, dass die Erhöhung des Zeigers weniger Male zu einigen großen Leistungsverbesserungen führen kann. Ich bin mir nicht sicher, warum das so ist, obwohl Sie in der Schleife viel mehr bitgewiäre Operationen ausführen, es ist immer noch besser, weniger Iterationen auf dem Zeiger zu machen.

Anstatt über 32 Bit -Pixel mit einem UINT32 -Iterate über zwei Pixel mit UINT64 zu iterieren und doppelt so hoch wie die Operationen in einem Zyklus zu iterieren.

Im Folgenden wird zwei Pixel gelesen und diese geändert (natürlich wird es mit Bildern mit ungerade Breite fehlschlagen, aber nur zum Testen).

    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());

    }

Der folgende Code macht es Pixel von Pixel und ist ungefähr 70% langsamer als der vorherige:

    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());
    }

Könnte jemand klarstellen, warum der Zeiger einen teureren Betrieb inkrementiert als ein paar bitgewiäre Operationen?

Ich verwende .NET 4 Framework.

Könnte so etwas für C ++ wahr sein?

NB. 32 Bit gegenüber 64 Bit Das Verhältnis der beiden Methoden ist gleich. Bei 64 gegenüber 32 Bit sind jedoch 20% langsamer?

Bearbeiten: Wie von Porges und ARUL vorgeschlagen, könnte dies auf eine verringerte Anzahl von Speicherlesungen und Verzweigungsaufwand zurückzuführen sein.

Edit2:

Nach einigen Tests scheint es, dass das Lesen aus dem Gedächtnis weniger Zeit die Antwort ist:

Mit diesem Code unter der Annahme, dass die Bildbreite um 5 teilbar ist, erhalten Sie 400% schneller:

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

Dann verwenden Sie dies:

            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;
            }
War es hilfreich?

Lösung

Dies ist eine Technik als bekannt als als Schlaufe abrollen. Der Hauptleistungsvorteil sollte durch die Reduzierung des Verzweigungsaufwandes zurückzuführen sein.

Als Randnotiz können Sie es mit einer Bitmaske ein wenig beschleunigen:

*((UInt64 *)pointer) &= 0xFFFFFF00FFFFFF00ul;

Andere Tipps

Es ist nicht die Inkrementierung des Zeigers, der langsamer ist, sondern das Lesen aus dem Speicher. Mit 32-Bit-Einheiten machen Sie doppelt so viele Lesevorgänge.

Sie sollten es wieder schneller finden, wenn Sie einmal in der 64-Bit-Version zweimal anstelle von zweimal schreiben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top