本文测试了托管和非托管两种情况下,对数据访问的性能差异。数组长度为10M,执行叠加操作(即 arr[i] = arr[i] + arr[i+1]
),执行100次求平均时间。
测试环境:
- 使用机器为:AMD 3700X(8T16C), 16 GB DDR4 2400, 512GB NVME
- 使用C#版本为4.7.2
注:StopWatch类是笔者自定义的计时工具,读者请使用一般计时方法替换。
public unsafe static void TestA()
{
int count = 10_000_000;
double[] ds = new double[count];
var sws = StopWatch.Array(100);
foreach (var sw in sws)
{
sw.Start("T1:Pointer");
// 非托管代码
unsafe
{
fixed (double* dp = ds)
{
for (int i = 0; i < count - 1; i++)
dp[i] += dp[i + 1];
}
}
// 托管代码
sw.Start("T2:Managed");
for (int i = 0; i < count - 1; i++)
ds[i] += ds[i + 1];
sw.StopAll();
}
StopWatch.Analyze(sws);
}
运行结果
通过以下结果可以看出,在运行次数少的情况下,非托管代码的性能较托管代码强5%。而在测试次数增加到500次以后,结果更加稳定。笔者还做了其他一些数组操作的测试,也基本符合出这个结果。所以可以得到结果,在测试次数少的情况下,非托管代码性能略微强于托管代码,但提升有限;而当测试次数达到一定次数,性能差别基本可以忽略。所以,即便是在性能要求特别苛刻的情况下也不建议使用,因为非托管代码性能提升非常有限,却带来了一定的安全性的隐患(毕竟非托管指针更不安全)。所以,非托管的代码更多的目的是为了与一些C/C++编写的类库进行调用,而不是为了性能提升。
# 100 次的运行结果
------------ Results.Median ------------
T1:Pointer: 19.0177 ms.
T2:Managed: 20.0183 ms.
------------ Results.Sum ------------
T1:Pointer: 1981.78 ms.
T2:Managed: 2040.88 ms.
------------ Results.Average ------------
T1:Pointer: 19.82 ms.
T2:Managed: 20.41 ms.
# 500次的运行结果
------------ Results.Median ------------
T1:Pointer: 20.0183 ms.
T2:Managed: 20.0185 ms.
------------ Results.Sum ------------
T1:Pointer: 10118.20 ms.
T2:Managed: 10266.31 ms.
------------ Results.Average ------------
T1:Pointer: 20.24 ms.
T2:Managed: 20.53 ms.