Domanda

Ho fatto alcune operazioni bitmap non sicure e hanno scoperto che aumentando il puntatore meno volte può portare ad alcuni grandi miglioramenti delle prestazioni. Io non so perché è così che, anche se non molto di più le operazioni bit per bit nel ciclo, è ancora meglio fare meno iterazioni sul puntatore.

Così, per esempio, invece di iterazione oltre 32 pixel bit con un UInt32 iterate su due pixel con UInt64 e turistiche due volte le operazioni in un ciclo.

Quanto segue fa leggendo due pixel e comandate (ovviamente sarà sicuro con fotografie con larghezza dispari, ma il suo solo per il test).

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

    }

Il codice seguente fa pixel per pixel ed è circa il 70% più lento rispetto al precedente:

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

Qualcuno potrebbe chiarire perché è incrementare il puntatore un'operazione più costosa rispetto a fare un paio operazioni bit per bit?

Sto usando .NET framework 4.

Potrebbe qualcosa come questo essere vero per C ++?

NB. 32 bit vs 64 bit il rapporto dei due metodi è uguale, ma in entrambi i modi sono come 20% più lento a 64 vs 32 bit?

EDIT: Come suggerito da Porges e Arul questo potrebbe essere a causa della riduzione del numero di memoria letture e la ramificazione in testa .

EDIT2:

Dopo qualche test, sembra che la lettura dalla memoria meno tempo è la risposta:

Con questo codice assumendo l'immagine larghezza è divisibile per 5 si ottiene 400% più veloce:

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

Quindi utilizzare questo:

            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;
            }
È stato utile?

Soluzione

Si tratta di una tecnica nota come srotolamento del ciclo. Il principale vantaggio delle prestazioni dovrebbe venire dalla riduzione della ramificazione in testa.

Come nota a margine, si potrebbe accelerare su una punta utilizzando una maschera di bit:

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

Altri suggerimenti

Non è il incrementando il puntatore che è più lento, ma la lettura dalla memoria. Con le unità a 32 bit, si sta facendo il doppio di legge.

Si dovrebbe trovare più velocemente di nuovo se si scrive una volta invece di due volte nella versione a 64 bit.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top