【问题标题】:C# unsafe performance vs unmanaged PInvoke callC# 不安全性能与非托管 PInvoke 调用
【发布时间】:2021-04-01 11:17:44
【问题描述】:

我正在运行一个处理位图图像的应用程序。现在我正在寻找一种快速交换“Format24bppRgb”位图图像的“红色”和“蓝色”值的方法。在我的 C# 代码中,我的第一次尝试是使用不安全的代码片段:

var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
    ImageLockMode.ReadWrite, bmp.PixelFormat);
unsafe
{
    byte* array = (byte*)bmpData.Scan0.ToPointer();
    byte temp;
    for (int x = 0; x < bmp.Width * bmp.Height * 3; x = x + 3) {
        temp = *(array + x + 2);
        *(array + x + 2) = *(array + x);
        *(array + x) = temp;
    }
}

对于我使用的位图大小,这大约需要 50-70 毫秒。现在我尝试通过 pinvoke 调用在外部库(基于 C++)中完成这项工作:

[DllImport("ByteSwitch.dll")] 
public static extern IntPtr ChangeRB(IntPtr data, int width, int height);

data = ChangeRB(bmpData.Scan0, bmp.Width, bmp.Height);

定义如下:

extern "C" __declspec(dllexport) void* ChangeRB(void* xArray, int xHeight, int xWidth);

void* ChangeRB(void* array, int height, int width)
{
    unsigned char* _array = (unsigned char*)array;
    char temp;
    for (int x = 0; x < height * width * 3; x = x + 3)
    {
        temp = _array[x + 2];
        _array[x + 2] = _array[x];
        _array[x] = temp;
    }
    return _array;
}

这个调用大约需要 1 毫秒!所以我无法在这里解释巨大的性能差异 - 或者说非托管 pinvoke 真的比“不安全”代码片段快得多?

【问题讨论】:

  • 将托管/非托管边界视为一个边界。跨越这个边界是有代价的。在您的第一种情况下,您正在循环中一遍又一遍地跨越该边界。在 P/Invoke 案例中,您正在跨越该边界,在另一侧完成工作,然后返回。
  • 你肯定不需要 unsafe 或 pinvoke 在这里。在纯 C# 中必须有一个很好的方法来做到这一点。
  • @DavidHeffernan:如果有在纯 C# 中执行此操作的好方法,我将不胜感激。
  • 您确定您测量的是正确的东西吗?您必须在发行版中运行 c# 版本并且不附加调试器。
  • 您是否在没有附加调试器的情况下进行测试(Visual Studio 中的 ctrl f5)?它大大减慢了 .net 代码的执行速度

标签: c# c++ computer-vision pinvoke unmanaged


【解决方案1】:

性能问题不是来自互操作,也不是来自 C#,而是因为您在循环中使用了位图的WidthHeight。两者都是内部call a GDI Plus API

public int Width {
    get {
        int width; 
 
        int status = SafeNativeMethods.Gdip.GdipGetImageWidth(new HandleRef(this, nativeImage), out width);
 
        if (status != SafeNativeMethods.Gdip.Ok)
            throw SafeNativeMethods.Gdip.StatusException(status);
 
        return width;
    }
}

请注意,您不会在 C/C++ 案例中执行此操作...您传递了预先计算的高度和宽度。因此,如果您为此更改 C# 版本:

unsafe
{
    byte* array = (byte*)bmpData.Scan0.ToPointer();
    byte temp;
    var max = bmp.Width * bmp.Height * 3;
    for (int x = 0; x < max; x = x + 3) {
        temp = *(array + x + 2);
        *(array + x + 2) = *(array + x);
        *(array + x) = temp;
    }
}

它可能在全球范围内运行得更快。您也可以使用这样的安全版本:

private static void ChangeSafe(Bitmap bmp, BitmapData bmpData)
{
    var array = bmpData.Scan0;
    byte temp;
    var max = bmp.Width * bmp.Height * 3;
    for (var x = 0; x < max; x = x + 3)
    {
        temp = Marshal.ReadByte(array + x + 2);
        Marshal.WriteByte(array + x + 2, Marshal.ReadByte(array + x));
        Marshal.WriteByte(array + x, temp);
    }
}

它稍微慢一些,但避免了不安全代码的需要。

【讨论】:

  • 你就是那个男人 - 它实际上是宽度和高度。此外 - 也感谢“安全”版本
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-19
  • 1970-01-01
  • 2013-10-11
  • 2013-09-30
  • 1970-01-01
相关资源
最近更新 更多