Почему мой небезопасный блок кода медленнее, чем мой безопасный код? - PullRequest
7 голосов
/ 03 мая 2010

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

Вот моя "безопасная" версия:

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 вместо указателя:

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

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

Спасибо

Ответы [ 3 ]

9 голосов
/ 03 мая 2010

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

Bitmap.BackBufferStride*Bitmap.PixelHeight

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

5 голосов
/ 03 мая 2010

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

0 голосов
/ 03 мая 2010

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

условие остановки, если if рассчитано в небезопасной версии, а не в сейфе

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

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

...