2010-05-03 3 views
7

Я пытаюсь написать код, который будет эффективно обрабатывать видеокадры. Я получаю фреймы как System.Windows.Media.Imaging.WriteableBitmap. В целях тестирования я просто применяю простой пороговый фильтр, который обрабатывает изображение формата BGRA и назначает каждый пиксель либо черным, либо белым, в зависимости от среднего значения BGR пикселей.Почему мой небезопасный код блокируется медленнее, чем мой безопасный код?

Вот мой "Safe" версия:

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

Вот что я думаю это прямой перевод с использованием небезопасного кода блока и WriteableBitmap Back Buffer вместо 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(); 

} 

Это мой первый набег на небезопасные блоки кода и указатели, поэтому, возможно, логика не оптимальна.

Я протестировал оба блока кода на один и то же 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)); 

Так я анализирую то же изображение. Испытательный проход входящего потока видеокадров:

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 

Почему моя небезопасная версия всегда медленнее? Это связано с использованием заднего буфера? Или я делаю что-то неправильно?

Благодаря

+0

Выполняете ли вы эти тесты в сборке Release? –

+0

Нет, это сейчас в сборке отладчика. –

+3

Я рекомендую выполнять тесты производительности в сборке релизов для получения значимых результатов. Вы можете обнаружить, что существует меньше разницы в производительности, чтобы оправдать использование «небезопасной» версии, если вы позволите оптимизатору иметь свой путь с кодом. –

ответ

9

Могут быть, потому что ваша небезопасная версия делает умножение и свойство доступ:

Bitmap.BackBufferStride*Bitmap.PixelHeight 

На каждой итерации цикла. Сохраните результат в переменной.

+0

Не уверен, что это проблема, но это определенно будет способствовать этому. – McAden

+1

Вы правы! Я был настолько обернут, пытаясь понять, как работает небезопасный блок, и указатели, что я был слеп к моей логической операции условия итератора. Хранение этого в переменной действительно опустило меня ниже «безопасной» версии, хотя на самом деле этого недостаточно, чтобы быть игровым устройством. Если есть другие вещи, которые я делаю, это не оптимально, я все уши, если нет, еще раз спасибо за быстрое исправление! –

+1

Это не проблема умножения, это то, что вы обращаетесь к свойствам (BackBufferStride и PixelHeight, это настоящий убийца. Я знаю, что вы работаете с WPF, но для Silverlight я создал обертку, которая кэширована все значения свойств растрового изображения для получения требуемой производительности. –

5

Еще одна оптимизация, будь то безопасный или небезопасный код: Остановить деление на 3 внутри вашей петли. Умножьте свой порог на 3 один раз, вне цикла. Вам нужно будет использовать некий тип, отличный от byte, но это не должно быть проблемой. На самом деле, вы уже используете более крупный тип данных, чем byte :)

+0

+1: Это тоже помогло! Я получал диапазон 26-30 мс до и после выполнения одиночного умножения вне цикла, как вы предположили, добрался до диапазона 17-20 мс. Не уверен, что смогу спуститься намного ниже. –

+0

@jomtois Я пробовал несколько вещей ... но если мои тесты что-то доказывают, это то, что микро-оптимизация обычно не стоит. Я попытался использовать некоторые битмаски и использовать что-то вроде 'while (pBackBuffer Thorarin

0

Сложно сказать, не профилируя код, тем более, что код очень отличается (хотя на первый взгляд он похож) некоторые ключевые моменты (и они все только предположение)

условию остановки, если, если рассчитываются в небезопасной версии не в безопасном

  • индексов для массива pixelArray может быть рассчитан только один раз Eventhough они используются дважды.
  • , даже если они не «кэшированные», добавив числа вместе без сохранения их (в отличие от ++ р) будет по-прежнему быстрее (меньше инструкций и меньше доступа к памяти)
  • вы не фиксирования растровые в безопасной версии
  • pixelArray [я], pixelArray [я + 1], pixelArray [я + 2] может получить хранятся в местных делая доступ к ним во второй раз потенциально быстрее, чем снова итерируя указатель.
  • у вас есть дополнительное назначение в небезопасном коде (pCOpy = pBackBuffer) и дополнительный прирост (pBackBuffer ++;)

Это все идеи, которые я могу придумать. Надеюсь, это поможет

+0

Я подозреваю, что состояние остановки - большой нападающий, как упоминалось выше. Я также проверю некоторые из ваших других идей и идею Торарина выше. В основном я хочу переместить указатель в 4 байтовых фрагментах, которые представляют пиксель. Мне нужно допросить и изменить первые три байта в куске и пропустить четвертый. Я не знаю, какие микро-оптимизации являются лучшими для этого. –

 Смежные вопросы

  • Нет связанных вопросов^_^