Domanda

Ho un metodo che deve essere veloce come il possibile, si usa puntatori di memoria non sicuri e la mia prima incursione in questo tipo di codifica in modo so che probabilmente può essere più veloce.

    /// <summary>
    /// Copies bitmapdata from one bitmap to another at a specified point on the output bitmapdata
    /// </summary>
    /// <param name="sourcebtmpdata">The sourcebitmap must be smaller that the destbitmap</param>
    /// <param name="destbtmpdata"></param>
    /// <param name="point">The point on the destination bitmap to draw at</param>
    private static unsafe void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
    {
        // calculate total number of rows to draw.
        var totalRow = Math.Min(
            destbtmpdata.Height - point.Y,
            sourcebtmpdata.Height);


        //loop through each row on the source bitmap and get mem pointers
        //to the source bitmap and dest bitmap
        for (int i = 0; i < totalRow; i++)
        {
            int destRow = point.Y + i;

            //get the pointer to the start of the current pixel "row" on the output image
            byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride);
            //get the pointer to the start of the FIRST pixel row on the source image
            byte* srcRowPtr = (byte*)sourcebtmpdata.Scan0 + (i * sourcebtmpdata.Stride);

            int pointX = point.X;
            //the rowSize is pre-computed before the loop to improve performance
            int rowSize = Math.Min(destbtmpdata.Width - pointX, sourcebtmpdata.Width);
            //for each row each set each pixel
            for (int j = 0; j < rowSize; j++)
            {
                int firstBlueByte = ((pointX + j)*3);

                int srcByte = j *3;
                destRowPtr[(firstBlueByte)] = srcRowPtr[srcByte];
                destRowPtr[(firstBlueByte) + 1] = srcRowPtr[srcByte + 1];
                destRowPtr[(firstBlueByte) + 2] = srcRowPtr[srcByte + 2];
            }


        }
    }

Quindi c'è qualcosa che può essere fatto per rendere questo più veloce? Ignorare il todo per ora, malato correzione che poi una volta che ho alcune misurazioni delle prestazioni di base.

UPDATE:. Spiacenti, avrebbe dovuto menzionare che il motivo per cui io sto usando questo invece di Graphics.DrawImage è perché im implementazione multi-threading e per questo non posso usare DrawImage

UPDATE 2: Non sono ancora soddisfatto della prestazione e sono sicuro che ci sono un paio di ms che si possono avere

.
È stato utile?

Soluzione

Non c'era qualcosa di fondamentalmente sbagliato con il codice che non posso credere che non ho notato fino ad ora.

byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride);

Questo diventa un puntatore alla riga di destinazione, ma non ottiene la colonna che è la copia a, che nel vecchio codice viene fatto all'interno del ciclo rowsize. Ora sembra:

byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride) + pointX * 3;

Così ora abbiamo il puntatore corretto per i dati di destinazione. Ora siamo in grado di sbarazzarsi di quel ciclo for. Utilizzando suggerimenti da Vilx- e Rob il codice ora appare come:

        private static unsafe void CopyBitmapToDestSuperFast(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
    {
        //calculate total number of rows to copy.
        //using ternary operator instead of Math.Min, few ms faster
        int totalRows = (destbtmpdata.Height - point.Y < sourcebtmpdata.Height) ? destbtmpdata.Height - point.Y : sourcebtmpdata.Height;
        //calculate the width of the image to draw, this cuts off the image
        //if it goes past the width of the destination image
        int rowWidth = (destbtmpdata.Width - point.X < sourcebtmpdata.Width) ? destbtmpdata.Width - point.X : sourcebtmpdata.Width;

        //loop through each row on the source bitmap and get mem pointers
        //to the source bitmap and dest bitmap
        for (int i = 0; i < totalRows; i++)
        {
            int destRow = point.Y + i;

            //get the pointer to the start of the current pixel "row" and column on the output image
            byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride) + point.X * 3;

            //get the pointer to the start of the FIRST pixel row on the source image
            byte* srcRowPtr = (byte*)sourcebtmpdata.Scan0 + (i * sourcebtmpdata.Stride);

            //RtlMoveMemory function
            CopyMemory(new IntPtr(destRowPtr), new IntPtr(srcRowPtr), (uint)rowWidth * 3);

        }
    }

Copia di un'immagine di 500x500 a un'immagine 5000x5000 in una griglia 50 volte preso: 00: 00: 07.9948993 sec. Ora, con le modifiche di cui sopra ci vogliono 00: 00: 01,8714263 sec. Molto meglio.

Altri suggerimenti

Beh ... io non sono sicuro se i formati di dati .NET bitmap sono tutto compatibile con le funzioni di Windows gdi32 di ...

Ma uno dei primi API Win32 che ho imparato è BitBlt:

BOOL BitBlt(
  HDC hdcDest, 
  int nXDest, 
  int nYDest, 
  int nWidth, 
  int nHeight, 
  HDC hdcSrc, 
  int nXSrc, 
  int nYSrc, 
  DWORD dwRop
);

Ed è stato il più veloce modo per copiare i dati in giro, se non ricordo male.

Ecco la firma BitBlt PInvoke per l'uso in C # e le relative informazioni di utilizzo, un grande letto per un qualsiasi lavoro con grafica ad alte prestazioni in C #:

Sicuramente vale la pena dare un'occhiata.

Il ciclo interno è dove si vuole concentrare un sacco di tempo (ma, fare misure per assicurarsi che)

for  (int j = 0; j < sourcebtmpdata.Width; j++)
{
    destRowPtr[(point.X + j) * 3] = srcRowPtr[j * 3];
    destRowPtr[((point.X + j) * 3) + 1] = srcRowPtr[(j * 3) + 1];
    destRowPtr[((point.X + j) * 3) + 2] = srcRowPtr[(j * 3) + 2];
}
  1. sbarazzarsi delle moltiplica e l'indicizzazione array (che è un moltiplicano sotto le cappe) e sostituirlo con un puntatore che si sta incrementando.

  2. Idem con il +1, +2, incrementa un puntatore.

  3. Probabilmente il compilatore non mancherà di tenere il calcolo point.X (controllo), ma fare una variabile locale per ogni evenienza. Non lo farà sulla singola iterazione, ma potrebbe ogni iterazione.

Si consiglia di guardare Eigen .

Si tratta di una libreria di template C ++ che utilizza SSE (2 e versioni successive) e le istruzioni AltiVec imposta con fallback elegante per il codice non vettorizzati .

  

veloce. (Vedi punto di riferimento).
   modelli di espressione permette di rimuovere in modo intelligente provvisori e consentire la valutazione pigra, quando ciò è appropriato - Eigen si prende cura di questo automaticamente e gestisce aliasing anche nella maggior parte dei casi
.   vettorizzazione esplicito viene eseguita per lo SSE (2 e successive) e set di istruzioni AltiVec, con fallback elegante per codice non vettorializzare. modelli di espressione permettono di effettuare queste ottimizzazioni a livello globale per le espressioni intere.
   Con oggetti di dimensione fissa, allocazione dinamica della memoria viene evitata, e gli occhielli sono srotolato quando questo ha un senso.
   Per le grandi matrici, particolare attenzione è rivolta alla cache-friendly.

Si potrebbe implementare si lavora in C ++ e poi chiami da C #

Non è sempre necessario utilizzare i puntatori per ottenere una buona velocità. Questo dovrebbe essere entro un paio di ms dell'originale:

        private static void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
    {
        byte[] src = new byte[sourcebtmpdata.Height * sourcebtmpdata.Width * 3];
        int maximum = src.Length;
        byte[] dest = new byte[maximum];
        Marshal.Copy(sourcebtmpdata.Scan0, src, 0, src.Length);
        int pointX = point.X * 3;
        int copyLength = destbtmpdata.Width*3 - pointX;
        int k = pointX + point.Y * sourcebtmpdata.Stride;
        int rowWidth = sourcebtmpdata.Stride;
        while (k<maximum)
        {
            Array.Copy(src,k,dest,k,copyLength);
            k += rowWidth;

        }
        Marshal.Copy(dest, 0, destbtmpdata.Scan0, dest.Length);
    }

Purtroppo non ho il tempo di scrivere una soluzione completa, ma vorrei considerare di usare la piattaforma di RtlMoveMemory () per spostare le righe nel suo complesso, non byte per byte. Questo dovrebbe essere molto più veloce.

Credo che la dimensione di riga e di limiti numerici falcata possono essere calcolati in anticipo.

E io precalcolata tutte le moltiplicazioni, con conseguente il seguente codice:

private static unsafe void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
{
    //TODO: It is expected that the bitmap PixelFormat is Format24bppRgb but this could change in the future
    const int pixelSize = 3;

    // calculate total number of rows to draw.
    var totalRow = Math.Min(
        destbtmpdata.Height - point.Y,
        sourcebtmpdata.Height);

    var rowSize = Math.Min(
        (destbtmpdata.Width - point.X) * pixelSize,
        sourcebtmpdata.Width * pixelSize);

    // starting point of copy operation
    byte* srcPtr = (byte*)sourcebtmpdata.Scan0;
    byte* destPtr = (byte*)destbtmpdata.Scan0 + point.Y * destbtmpdata.Stride;

    // loop through each row
    for (int i = 0; i < totalRow; i++) {

        // draw the entire row
        for (int j = 0; j < rowSize; j++)
            destPtr[point.X + j] = srcPtr[j];

        // advance each pointer by 1 row
        destPtr += destbtmpdata.Stride;
        srcPtr += sourcebtmpdata.Stride;
    }

}

Havn't testato a fondo, ma si dovrebbe essere in grado di ottenere che al lavoro.

Ho rimosso operazioni di moltiplicazione dal circuito (precalcolato invece) e rimossa la maggior parte delle ramificazioni e quindi dovrebbe essere in qualche modo più veloce.

Vorrei sapere se questo aiuta: -)

Io sto guardando il tuo codice C # e non riesco a riconoscere nulla familiare. Sembra tutto come una tonnellata di C ++. A proposito, sembra che DirectX / XNA ha bisogno di diventare il vostro nuovo amico. Solo i miei 2 centesimi. Non uccidete il messaggero.

Se si deve fare affidamento su CPU per fare questo: ho fatto alcune ottimizzazioni di layout 24-bit me stesso, e mi si può dire che la velocità di accesso alla memoria dovrebbe essere il collo di bottiglia. Utilizzare le istruzioni SSE3 per più veloce possibile l'accesso a byte. Questo significa che C ++ e linguaggio assembly incorporato. In puro C sarete il 30% più lento sulla maggior parte delle macchine.

Tenete a mente che le GPU moderne sono molto più veloci CPU in questo tipo di operazioni.

Non sono sicuro se questo darà una performance migliore, ma vedo il modello molto in Reflector.

int srcByte = j *3;
destRowPtr[(firstBlueByte)] = srcRowPtr[srcByte];
destRowPtr[(firstBlueByte) + 1] = srcRowPtr[srcByte + 1];
destRowPtr[(firstBlueByte) + 2] = srcRowPtr[srcByte + 2];

diventa:

*destRowPtr++ = *srcRowPtr++;
*destRowPtr++ = *srcRowPtr++;
*destRowPtr++ = *srcRowPtr++;

probabilmente ha bisogno di più le parentesi graffe.

Se la larghezza è fissa, si potrebbe forse srotolare l'intera linea in poche centinaia di righe. :)

Aggiorna

Si potrebbe anche provare a utilizzare un tipo più grande, ad esempio Int32 o Int64 per migliorare le prestazioni.

Bene, questo sta per essere abbastanza vicino alla linea di quanti ms si può uscire dell'algoritmo, ma sbarazzarsi della chiamata a Math.min e sostituirlo con un operatore ternario, invece.

In generale, facendo una chiamata di libreria richiede più di fare qualcosa da soli e ho fatto un semplice test driver per confermare questo per Math.min.

using System;
using System.Diagnostics;

namespace TestDriver
{
    class Program
    {
        static void Main(string[] args)
        {
            // Start the stopwatch
            if (Stopwatch.IsHighResolution)
            { Console.WriteLine("Using high resolution timer"); }
            else
            { Console.WriteLine("High resolution timer unavailable"); }
            // Test Math.Min for 10000 iterations
            Stopwatch sw = Stopwatch.StartNew();
            for (int ndx = 0; ndx < 10000; ndx++)
            {
                int result = Math.Min(ndx, 5000);
            }
            Console.WriteLine(sw.Elapsed.TotalMilliseconds.ToString("0.0000"));
            // Test trinary operator for 10000 iterations
            sw = Stopwatch.StartNew();
            for (int ndx = 0; ndx < 10000; ndx++)
            {
                int result = (ndx < 5000) ? ndx : 5000;
            }
            Console.WriteLine(sw.Elapsed.TotalMilliseconds.ToString("0.0000"));
            Console.ReadKey();
        }
    }
}

I risultati durante l'esecuzione del sopra sul mio computer, un processore Intel T2400 @ 1.83GHz. Inoltre, notare che v'è un po 'di variazione nei risultati, ma generalmente l'operatore Trinay è più veloce di circa 0,01 ms. Non è molto, ma nel corso di un grande insieme di dati sufficienti che si sommano.

  

Utilizzando alta risoluzione timer
  0.0539
  0,0402

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