在现代软件开发中,C#以其简洁的语法和强大的功能而广受欢迎。它通过消除手动内存管理的需求,提供了快速的编译时间、广泛的标准库以及其他实用功能。然而,对于需要大量数值计算的应用,其性能可能不尽如人意。本文将展示在必要时如何使用C#调用C++函数,并对其性能进行分析。
假设有大量随机矩形分布在(0, 0)和(2, 2)之间,需要计算这些矩形中有多少百分比位于(0, 0)和(1, 1)之间。将使用暴力方法来解决这个问题,以测试CPU的性能。算法的代码相当直接:为每个矩形生成四个介于(0, 2)之间的随机数,并将它们分配给矩形的角点。然后,计算有多少矩形位于所需的区间内。所有测试都在配备4GB内存的q6600上进行,测试对象为1000万个矩形。
这是纯C#的实现,用作性能比较的基准。对于1000万个矩形,这种方法大约需要146毫秒。
这是实现互操作的最简单方式。C#端的代码如下:
[DllImport("DllFuncs.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "nativef")]
public static extern float getPercentBBMarshal(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] BBox[] boxes,
int size);
这告诉运行时在名为"DllFuncs.dll"的本地库中使用cdecl调用约定查找名为"nativef"的函数。它还告诉运行时将C#的BBox数组透明地转换为C++数组。需要传递大小,因为C++数组不知道它们的长度。对应的C++函数如下:
struct BBox {
float x1, y1, x2, y2;
int isValid() {
return (x1 < 1) && (x2 < 1) && (y1 < 1) && (y2 < 1) && (x1 > 0) && (x2 > 0) && (y1 > 0) && (y2 > 0);
}
};
__declspec(dllexport) float __cdecl nativef(BBox * boxes, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += boxes[i].isValid();
}
return (float)sum / (float)size * 100;
}
Marshaling性能:使用计时器测量这个函数的性能,得到了一个有趣的结果。原生函数处理1000万个元素需要341毫秒,大约是C#等效函数所需时间的两倍!此外,对于1000个元素,Marshaling需要239.3毫秒,远远高于纯C#所需的0.054毫秒。显然,Marshaling增加了巨大的开销,随着工作量的增加,这种开销的相对重要性在减少。要了解这种开销的来源,需要知道Marshaling是如何工作的:
现在,可以很容易地看出性能不佳的原因。本质上是在分配1000万个矩形并复制它们的值。这是分配和移动大约160MB的数据!难怪性能如此糟糕。可能在问自己,为什么首先需要复制这个过程。有两个原因:
那么,是否有可能解决这些问题呢?让找出答案!
解决内存布局问题相对简单。它只是告诉运行时像C++一样在内存中布局结构。C++使用某些对齐规则顺序布局数据,C#可以使用以下方式模仿:
[StructLayout(LayoutKind.Sequential)]
struct BBox {
public float x1, y1, x2, y2;
// 矩形的角点
}
垃圾回收器问题可以通过使用fixed语句来解决。这个语句确保在语句的生命周期内内存不会被GC移动。fixed语句只能在编译时使用/unsafe选项的程序集中的不安全函数中使用。
[DllImport("DllFuncs.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe float nativef(IntPtr p, int size);
public static unsafe float getPercentBBInterop(BBox[] boxes) {
float result;
fixed (BBox* p = boxes) {
result = nativef((IntPtr)p, boxes.Length);
}
return result;
}
指针访问性能:该函数返回时间为115毫秒,比C#等效函数快约26%。随着委托给原生代码的函数复杂性的增加,性能提升可能会增加。
下面展示了处理不同数量元素时的性能图表:
https://github.com/debdattabasu/NativeInterop