2010-05-03 28 views
7

Estoy intentando escribir algún código que procese oportunamente los marcos de video. Estoy recibiendo los marcos como System.Windows.Media.Imaging.WriteableBitmap. Para fines de prueba, solo estoy aplicando un filtro de umbral simple que procesará una imagen de formato BGRA y asignará cada píxel a ser negro o blanco en función del promedio de los píxeles de BGR.¿Por qué mi bloque de código inseguro es más lento que mi código de seguridad?

Aquí está mi versión "segura":

public static void ApplyFilter(WriteableBitmap Bitmap, byte Threshold) 
{ 
    // Let's just make this work for this format 
    if (Bitmap.Format != PixelFormats.Bgr24 
     && Bitmap.Format != PixelFormats.Bgr32) 
    { 
     return; 
    } 

    // Calculate the number of bytes per pixel (should be 4 for this format). 
    var bytesPerPixel = (Bitmap.Format.BitsPerPixel + 7)/8; 

    // Stride is bytes per pixel times the number of pixels. 
    // Stride is the byte width of a single rectangle row. 
    var stride = Bitmap.PixelWidth * bytesPerPixel; 

    // Create a byte array for a the entire size of bitmap. 
    var arraySize = stride * Bitmap.PixelHeight; 
    var pixelArray = new byte[arraySize]; 

    // Copy all pixels into the array 
    Bitmap.CopyPixels(pixelArray, stride, 0); 

    // Loop through array and change pixels to black/white based on threshold 
    for (int i = 0; i < pixelArray.Length; i += bytesPerPixel) 
    { 
     // i=B, i+1=G, i+2=R, i+3=A 
     var brightness = 
       (byte)((pixelArray[i] + pixelArray[i+1] + pixelArray[i+2])/3); 

     var toColor = byte.MinValue; // Black 

     if (brightness >= Threshold) 
     { 
      toColor = byte.MaxValue; // White 
     } 

     pixelArray[i] = toColor; 
     pixelArray[i + 1] = toColor; 
     pixelArray[i + 2] = toColor; 
    } 
    Bitmap.WritePixels(
     new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight), 
     pixelArray, stride, 0 
    ); 
} 

Esto es lo que que es una traducción directa utilizando un bloque de código inseguro y el WriteableBitmap Volver búfer en lugar de la forebuffer:

public static void ApplyFilterUnsafe(WriteableBitmap Bitmap, byte Threshold) 
{ 
    // Let's just make this work for this format 
    if (Bitmap.Format != PixelFormats.Bgr24 
     && Bitmap.Format != PixelFormats.Bgr32) 
    { 
     return; 
    } 

    var bytesPerPixel = (Bitmap.Format.BitsPerPixel + 7)/8; 

    Bitmap.Lock(); 

    unsafe 
    { 
     // Get a pointer to the back buffer. 
     byte* pBackBuffer = (byte*)Bitmap.BackBuffer; 

     for (int i = 0; 
      i < Bitmap.BackBufferStride*Bitmap.PixelHeight; 
      i+= bytesPerPixel) 
     { 
      var pCopy = pBackBuffer; 
      var brightness = (byte)((*pBackBuffer 
            + *++pBackBuffer 
            + *++pBackBuffer)/3); 
      pBackBuffer++; 

      var toColor = 
        brightness >= Threshold ? byte.MaxValue : byte.MinValue; 

      *pCopy = toColor; 
      *++pCopy = toColor; 
      *++pCopy = toColor;      
     } 
    } 

    // Bitmap.AddDirtyRect(
    //   new Int32Rect(0,0, Bitmap.PixelWidth, Bitmap.PixelHeight)); 
    Bitmap.Unlock(); 

} 

Esta es mi primera incursión en bloques de código y punteros inseguros, por lo que quizás la lógica no sea óptima.

He probado ambos bloques de código en los mismos utilizando WriteableBitmaps:

var threshold = Convert.ToByte(op.Result); 
var copy2 = copyFrame.Clone(); 
Stopwatch stopWatch = new Stopwatch(); 
stopWatch.Start(); 
BinaryFilter.ApplyFilterUnsafe(copyFrame, threshold); 
stopWatch.Stop(); 

var unsafesecs = stopWatch.ElapsedMilliseconds; 
stopWatch.Reset(); 
stopWatch.Start(); 
BinaryFilter.ApplyFilter(copy2, threshold); 
stopWatch.Stop(); 
Debug.WriteLine(string.Format("Unsafe: {1}, Safe: {0}", 
       stopWatch.ElapsedMilliseconds, unsafesecs)); 

Así que estoy analizando la misma imagen. Una ejecución de prueba de una secuencia entrante de cuadros de video:

Unsafe: 110, Safe: 53 
Unsafe: 136, Safe: 42 
Unsafe: 106, Safe: 36 
Unsafe: 95, Safe: 43 
Unsafe: 98, Safe: 41 
Unsafe: 88, Safe: 36 
Unsafe: 129, Safe: 65 
Unsafe: 100, Safe: 47 
Unsafe: 112, Safe: 50 
Unsafe: 91, Safe: 33 
Unsafe: 118, Safe: 42 
Unsafe: 103, Safe: 80 
Unsafe: 104, Safe: 34 
Unsafe: 101, Safe: 36 
Unsafe: 154, Safe: 83 
Unsafe: 134, Safe: 46 
Unsafe: 113, Safe: 76 
Unsafe: 117, Safe: 57 
Unsafe: 90, Safe: 41 
Unsafe: 156, Safe: 35 

¿Por qué mi versión insegura es siempre más lenta? ¿Es debido al uso del buffer de respaldo? ¿O estoy haciendo algo mal?

Gracias

+0

¿Está ejecutando estas pruebas en una compilación de versión? –

+0

No, esto está en una compilación de depurador en este momento. –

+3

Recomiendo ejecutar pruebas de rendimiento en una versión de lanzamiento para obtener resultados significativos. Puede encontrar que hay una brecha de rendimiento menor para justificar el uso de una versión "insegura" si permite que el optimizador cumpla con el código. –

Respuesta

9

Tal vez porque su versión insegura está haciendo una multiplicación y la propiedad de acceso:

Bitmap.BackBufferStride*Bitmap.PixelHeight 

En cada iteración del bucle. Almacenar el resultado en una variable.

+0

No estoy seguro de que sea EL problema, pero definitivamente contribuiría a ello. – McAden

+1

¡Estás en lo correcto! Estaba tan absorto tratando de descubrir cómo funcionaba el bloque inseguro y los indicadores, que estaba ciego a la operación lógica de mi condición de iterador. Almacenar eso en una variable realmente me dejó por debajo de la versión "segura", aunque no lo suficiente como para cambiar las reglas del juego. Si hay otras cosas que estoy haciendo que no son óptimas, soy todo oídos, si no, ¡gracias de nuevo por la solución rápida! –

+1

No es la multiplicación lo que es el mayor problema, es el hecho de que está accediendo a las propiedades (BackBufferStride y PixelHeight, este es el verdadero asesino. Sé que está trabajando con WPF, pero para Silverlight creé un contenedor que almacenaba en caché todos los valores de propiedad del mapa de bits para obtener el rendimiento requerido. –

5

Una optimización adicional, ya sea en el código seguro o inseguro: Pare de dividir por 3 dentro de su bucle. Multiplique su umbral por 3 una vez, fuera del ciclo. Tendrá que utilizar algún tipo que no sea byte, pero eso no debería ser un problema. En realidad, ya está usando un tipo de datos más grande que byte :)

+0

+1: ¡Esto también me ayudó! Estaba obteniendo un rango de 26-30 ms antes y ahora realizar la única multiplicación fuera del ciclo, como sugirió, lo bajó al rango de 17-20 ms. No estoy seguro de que pueda bajar mucho más bajo. –

+0

@jomtois He intentado algunas cosas ... pero si mis pruebas demuestran algo, es que la micro-optimización generalmente no vale la pena. Intenté usar algunas máscaras de bits y usar algo como 'while (pBackBuffer Thorarin

0

Es difícil decir sin perfilar el código, especialmente porque el código es muy diferente (aunque al principio se ve similar) algunos puntos clave (y son todo a sólo especulaciones)

la condición de parada si el caso se calcula en la versión insegura no en la caja fuerte

  • los índices de la matriz pixelArray solamente podría calcularse una vez apesar de que se utilizan dos veces.
  • , incluso si no están "en caché", agregando los números juntos sin almacenar ellos (en contraposición a ++ p) seguiría siendo ser más rápido (menos instrucciones y menos acceso a la memoria)
  • no están bloqueando el mapa de bits en la versión segura
  • pixelArray [i], pixelArray [i + 1], pixelArray [i + 2] puede ser que consiga almacenado en los locales que hacen acceder a ellos la segunda vez potencialmente más rápido que la iteración el puntero nuevo.
  • que tienen una asignación adicional en el código insegura (PCOPY = pBackBuffer) y un incremento adicional (pBackBuffer ++;)

Eso es todo las ideas que pueda ocurrir. Espero que ayude

+0

Sospecho que la condición de parada es el gran bateador, como se mencionó anteriormente. Revisaré también algunas de tus otras ideas y la idea de @ Thorarin. Básicamente, quiero mover el puntero a lo largo de los segmentos de 4 bytes que representan el píxel. Necesito interrogar y cambiar los primeros tres bytes en el fragmento y omitir el cuarto. No sé qué micro optimizaciones son las mejores para esto. –

Cuestiones relacionadas