Pregunta

Tengo un método que tiene que ser tan rápido como sea posible, utiliza punteros de memoria inseguras y es mi primera incursión en este tipo de codificación, así que sé que probablemente puede ser más rápido.

    /// <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];
            }


        }
    }

Entonces, ¿hay algo que se pueda hacer para hacer esto más rápido? Ignorar el TODO, por ahora, arreglo del mal que luego una vez que tengo algunas mediciones de rendimiento de línea de base.

ACTUALIZACIÓN:. Lo sentimos, debería haber mencionado que la razón por la que estoy usando esto en vez de Graphics.DrawImage se debe a la implementación de im multi-threading y debido a que no puedo usar DrawImage

Actualización 2: Todavía no estoy satisfecho con el rendimiento y estoy seguro de que hay un poco más de MS que se pueden tener

.
¿Fue útil?

Solución

Hay algo fundamentalmente mal con el código que no puedo creer que me había dado cuenta hasta ahora.

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

Esto consigue un puntero a la fila de destino pero no consigue la columna que se está copiando a, que en el código anterior se lleva a cabo dentro del bucle RowSize. Ahora parece que:

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

Así que ahora tenemos el puntero correcto para los datos de destino. Ahora podemos deshacernos de ese bucle. Uso de sugerencias de Vilx- y Rob el código ahora se ve así:

        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 de una imagen de 500x500 a una imagen de 5000x5000 en una cuadrícula de 50 veces llevaron: 00: 00: 07.9948993 segundos. Ahora, con los cambios que se necesita por encima de 00: 00: 01,8714263 segundos. Mucho mejor.

Otros consejos

Bueno ... No estoy seguro de si los formatos de datos .NET de mapa de bits son completamente compatible con las funciones de GDI32 de Windows ...

Sin embargo, uno de los primeros pocos API de Win32 que aprendí fue BitBlt:

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

Y fue el más rápido modo para copiar datos en todo, si no recuerdo mal.

Aquí está la firma BitBlt PInvoke para su uso en C # y la información de uso relacionada, una gran lectura para cualquiera trabajar con gráficos de alto rendimiento en C #:

Definitivamente vale la pena un vistazo.

El bucle interior es la que desea concentrar una gran cantidad de su tiempo (pero, hacer mediciones para asegurarse)

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. Se puede olvidarse de los multiplica y la indexación array (que es una multiplican bajo las capuchas) y reemplazarlo con un puntero que se está incrementando.

  2. Lo mismo ocurre con el 1, 2, incrementar un puntero.

  3. Probablemente el compilador no mantendrá computación punto.x (cheque), pero hacer que una variable local por si acaso. No va a hacerlo en la única iteración, pero puede ser que cada iteración.

Es posible que desee ver en Eigen .

Es una biblioteca C ++ plantilla que utiliza SSE (2 y posterior) y la instrucción AltiVec establece con fallback elegante de código no vectorizado .

  

rápido. (Ver referencia).
   plantillas de expresiones permiten eliminar de forma inteligente los temporales y permitir la evaluación perezosa, cuando eso es apropiada - Eigen se encarga de esto automáticamente y se ocupa de aliasing también en la mayoría de los casos
.   vectorización explícita se realiza para la SSE (2 y más tarde) y conjuntos de instrucciones AltiVec, con fallback elegante de código no vectorizada. plantillas de expresiones permiten llevar a cabo estas optimizaciones a nivel mundial por expresiones enteras.
   Con objetos de tamaño fijo, asignación de memoria dinámica se evita, y los bucles se desenrollan cuando eso tiene sentido.
   Para las grandes matrices, se presta especial atención a la caché de uso.

Se puede aplicar a funcionar en C ++ y luego llama a que a partir de C #

No siempre es necesario utilizar punteros para conseguir una buena velocidad. Esto debe estar dentro de un par ms del original:

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

Por desgracia, no tienen el tiempo para escribir una solución completa, pero me gustaría ver en el uso de la plataforma RtlMoveMemory () función para mover filas en su conjunto, no byte a byte. Eso debería ser mucho más rápido.

Creo que los límites de tamaño y el número de filas de zancada se pueden calcular de antemano.

Y precalculados todas las multiplicaciones, dando como resultado el siguiente código:

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 probado a fondo, sino que debe ser capaz de conseguir que funcione.

He quitado operaciones de multiplicación desde el bucle (pre-calculado en vez) y se retira la mayoría de ramificaciones por lo que debe ser un poco más rápido.

Quiero saber si esto ayuda: -)

Estoy buscando en el código C # y no puedo reconocer algo familiar. Todo se ve como una tonelada de C ++. Por cierto, parece que DirectX / XNA necesita para convertirse en su nuevo amigo. Sólo mis 2 centavos. No matar al mensajero.

Si tiene que depender de la CPU para hacer esto: He hecho algunas optimizaciones de diseño de 24 bits a mí mismo, y yo puedo decir que la velocidad de acceso a la memoria debe ser su cuello de botella. Utilice instrucciones SSE3 para acceso más rápido posible byte a byte. Esto significa C ++ y lenguaje ensamblador embebido. En C puro podrás 30% más lento en la mayoría de las máquinas.

Tenga en cuenta que las GPU modernas son mucho más rápido que la CPU en este tipo de operaciones.

No estoy seguro de si esto va a dar a la capacidad, pero no veo el patrón mucho en el reflector.

Así que:

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

Se convierte en:

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

Probablemente necesita más apoyos.

Si la anchura es fija, probablemente podría desenrollar toda la línea en unos pocos cientos de líneas. :)

Actualizar

También podría tratar de usar un tipo más grande, por ejemplo, Int32 o Int64 para un mejor rendimiento.

Muy bien, esto va a estar bastante cerca de la línea de la cantidad de MS se puede salir del algoritmo, pero deshacerse de la llamada a Math.min y reemplazarlo con un operador ternario en su lugar.

En general, hacer una llamada a la librería tomará más tiempo de hacer algo por su cuenta y me hizo un controlador simple prueba para confirmar esto para 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();
        }
    }
}

Los resultados cuando se ejecuta lo anterior en mi equipo, un procesador Intel T2400 @ 1,83 GHz. Además, tenga en cuenta que hay un poco de variación en los resultados, pero en general el operador Trinay es más rápido en alrededor de 0,01 ms. Eso no es mucho, pero sobre un conjunto de datos suficientemente grande que se suman.

  

Uso de alta resolución del temporizador
  0,0539
  0,0402

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top