2010-05-03 15 views
7

Ich versuche, Code zu schreiben, der Videoframes zweckdienlich verarbeiten wird. Ich empfange die Rahmen als System.Windows.Media.Imaging.WriteableBitmap. Zu Testzwecken verwende ich einfach einen einfachen Schwellenwertfilter, der ein Bild im BGRA-Format verarbeitet und jedem Pixel basierend auf dem Durchschnitt der BGR-Pixel entweder Schwarz oder Weiß zuweist.Warum ist mein unsicherer Code langsamer als mein sicherer Code?

Hier ist meine "Safe" Version:

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

Hier ist, was ich ist denke eine direkte Übersetzung ist einen unsicheren Codeblock und die Writeablebitmap Back Buffer anstelle des forebuffer mit:

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

} 

Dies ist mein erster Ausflug in unsichere Code-Blöcke und Zeiger, also ist die Logik vielleicht nicht optimal.

Ich habe beide Codeblöcke auf den gleichen WriteableBitmaps getestet mit:

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

Ich Analyse das gleiche Bild So. Ein Testlauf eines eingehenden Videostreamerahmens:

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 

Warum ist meine unsichere Version immer langsamer? Liegt es an der Verwendung des Backpuffers? Oder mache ich etwas falsch?

Dank

+0

Führen Sie diese Tests in einem Release-Build? –

+0

Nein, das ist jetzt in einem Debugger-Build. –

+3

Ich empfehle, Leistungstests in einem Release-Build auszuführen, um aussagekräftige Ergebnisse zu erhalten. Sie können feststellen, dass es weniger Leistungslücken gibt, um die Verwendung einer "unsicheren" Version zu rechtfertigen, wenn Sie den Optimierer mit dem Code vertraut machen. –

Antwort

9

Vielleicht, weil Ihre unsichere Version einen mehrfach und Eigenschaftszugriff tut:

Bitmap.BackBufferStride*Bitmap.PixelHeight 

Auf jeder Schleifeniterationslatenzzeit. Speichern Sie das Ergebnis in einer Variablen.

+0

Nicht sicher, dass es DAS Problem ist, aber es würde definitiv dazu beitragen. – McAden

+1

Sie sind richtig! Ich war so versessen darauf, herauszufinden, wie der unsichere Block funktionierte und die Zeiger, dass ich für meine Iterator-Bedingungslogik-Operation blind war. Wenn ich das in einer Variablen aufbewahre, fiel ich tatsächlich unter die "sichere" Version, obwohl dies nicht wirklich genug war, um das Spiel zu verändern. Wenn es andere Dinge gibt, die ich mache, die suboptimal sind, bin ich ganz Ohr, wenn nicht, danke nochmal für die schnelle Lösung! –

+1

Es ist nicht die Multiplikation, die das Hauptproblem ist, es ist die Tatsache, dass Sie auf die Eigenschaften zugreifen (BackBufferStride und PixelHeight, das ist der wahre Killer. Ich weiß, dass Sie mit WPF arbeiten, aber für Silverlight ich einen Wrapper, der zwischengespeichert alle Eigenschaftswerte der Bitmap, um die erforderliche Leistung zu erhalten –

5

Eine weitere Optimierung, entweder in sicherem oder unsicheren Code: Stoppen Sie die Division durch 3 innerhalb Ihrer Schleife. Multiplizieren Sie Ihren Schwellenwert einmal außerhalb der Schleife mit 3. Sie müssen einen anderen Typ als byte verwenden, aber das sollte kein Problem sein. Tatsächlich verwenden Sie bereits einen größeren Datentyp als byte :)

+0

+1: Das half auch! Ich war etwa 26-30 ms Reichweite vor und jetzt die Durchführung der einzelnen multiplizieren außerhalb der Schleife, wie Sie vorgeschlagen, es in den Bereich von 17-20 ms. Nicht sicher, ich werde in der Lage, viel tiefer zu bekommen. –

+0

@jomtois Ich habe ein paar Dinge ausprobiert ... Aber wenn meine Tests irgendetwas beweisen, dann ist die Mikro-Optimierung normalerweise nicht wert.Ich habe versucht, einige Bitmasken zu verwenden und etwas wie while (pBackBuffer Thorarin

0

Es ist schwer zu sagen ohne Profiling des Codes, vor allem, da der Code sehr unterschiedlich ist (obwohl es zunächst ähnlich aussieht) einige wichtige Punkte (und sie sind alles nur Spekulationen)

die Stoppbedingung, wenn die wenn in der unsicheren Version berechnet wird nicht in den sicheren

  • die Indizes für das Array Pixelarray nur berechnet werden, könnte einmal obwohl sie zweimal verwendet werden.
  • auch wenn sie nicht „Cache“ hinzufügen die Zahlen zusammen, ohne sie zu speichern (im Gegensatz zu ++ p gegen) würde noch schneller sein (weniger Befehle und weniger Speicherzugriff)
  • Sie nicht das sind Sperren Bitmap in der sichere Version
  • pixelarray [i], pixelarray [i + 1], pixelarray [i + 2] könnte in Einheimischen gespeichert bekommen machen sie zum zweiten Mal potenziell schneller als Iterieren der Zeiger wieder erreichbar.
  • Sie haben eine zusätzliche Zuordnung in dem unsicheren Code (PCOPY = pBackBuffer) und eine zusätzliche Erhöhung (pBackBuffer ++;)

, dass alle Ideen, das ich mit oben kommen kann. Hoffe, es hilft

+0

Ich vermute, der Stoppzustand ist der große Hitter, wie oben erwähnt. Ich werde auch einige Ihrer anderen Ideen und @ Thorarin's Idee oben überprüfen. Grundsätzlich möchte ich den Zeiger in 4 Byte-Chunks verschieben, die das Pixel darstellen. Ich muss die ersten drei Bytes im Block abfragen und ändern und den vierten überspringen. Ich weiß nicht, welche Mikrooptimierungen dafür am besten sind. –