为什么我的不安全代码块比我的安全代码慢?

问题描述:

我正在尝试编写一些代码,可以方便地处理视频帧。我正在接收帧为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的后台缓冲区,而不是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版本中运行这些测试? – 2010-05-03 19:21:43

+0

现在不在调试器中。 – 2010-05-03 19:23:35

+3

我建议在发布版本中运行性能测试以获得有意义的结果。如果你让优化器与代码一起使用,你可能会发现性能差距不足以证明使用'不安全'版本的理由。 – 2010-05-03 20:12:04

也许是因为你的不安全的版本是做乘法和属性访问:

Bitmap.BackBufferStride*Bitmap.PixelHeight 

在每一个循环迭代。将结果存储在一个变量中。

+0

不知道这是问题所在,但肯定会对它有所贡献。 – McAden 2010-05-03 19:27:40

+1

你是对的!我非常想弄清楚不安全块如何工作和指针,我对我的迭代器条件逻辑操作一无所知。将它存储在一个变量中确实让我跌破了“安全”版本,尽管还不足以成为改变游戏规则的人。如果还有其他事情我做的是不是最优的,我全都是耳朵,如果不是的话,再次感谢您的快速解决! – 2010-05-03 19:30:25

+1

这不是繁复的主要问题,而是你正在访问的属性(BackBufferStride和PixelHeight,这是真正的杀手锏。我知道你正在使用WPF,但是对于Silverlight,我创建了一个缓存的包装器所有的位图属性值,以获得所需的性能 – 2010-05-03 20:25:44

在安全或不安全代码中进一步优化: 在循环中停止除以3。在循环之外将您的阈值乘以3。您需要使用byte以外的其他类型,但这不应该成为问题。实际上,你已经使用了比byte更大的数据类型:)

+0

+1:这也有帮助!我得到约26-30毫秒的范围之前和现在在循环之外执行单个乘法,正如你所建议的那样,将它缩小到17-20毫秒的范围内。不知道我能够降低得多。 – 2010-05-03 20:15:43

+0

@jomtois我试过几件事...但是如果我的测试证明什么的话,微型优化通常是不值得的,我尝试使用一些位掩码并使用'while(pBackBuffer Thorarin 2010-05-03 20:43:50

很难说出代码分析,尤其是代码非常不同(尽管它看起来很相似),但一些关键点(它们是一切都只是猜测)

停止条件,如果如果不安全的版本是不计算在安全

  • 的指数pixelArray 可能eventhough他们使用了两次只能使用一次 计算阵列。
  • 即使他们没有“缓存”,并称 的数字加在一起而不存储 他们(而不是++ P)将仍然 更快(更少的指令和 更少的内存存取)
  • 你是不是锁定位图在 安全版本
  • pixelArray [I],pixelArray [I + 1],pixelArray [I + 2] 可能会存储在当地人制作 访问它们第二次 可能比一次迭代 指针快。
  • 你在 不安全的代码(PCOPY = pBackBuffer)和 一个额外的增量额外分配(pBackBuffer ++;)

这就是所有的想法我可以拿出。希望它有帮助

+0

我怀疑停止条件是如上所述的大打者。我会检查一下你的其他想法和@ Thorarin的想法。基本上我想要将指针移动到代表像素的4个字节的块中。我需要询问并更改块中的前三个字节并跳过第四个字节。我不知道微型优化是最好的。 – 2010-05-03 19:59:27