[.NET学习笔记] - C++/CLI项目迁移至.NET5.0时Marshal::StructureToPtr的性能问题

背景

手头上有个C++/CLI项目,主要是用来封装C++的dll,方便.NET调用的。之前是在.NET FW 4.8 runtime上,最近想迁移至.NET Core runtime。当前的时间点有两个选择,一个是.NET Core 3.1,一个是.NET 5.0。一个是LTS版,一个是新鲜出炉版。没有多想,觉得.NET 5.0虽然很新,有很多坑不稳定,但不至于被我踩上,加上网上吹的那么多性能提升,首选了升级至.NET 5.0

C++/CLI项目的migrate

C++/CLI项目从.NET FW 迁移至.NET改动不大,操作如下:
在这里插入图片描述
[.NET学习笔记] - C++/CLI项目迁移至.NET5.0时Marshal::StructureToPtr的性能问题_第1张图片
主要就是选择新版的rumtime,由/clr更改为/clr:netcore。调整对应的.NET Traget Framework Versionv4.8 -> .NET Core 3.1 or .NET 5.0

分析

由于是个C++/CLI项目,对性能还是比较敏感的,虽然网上有很多性能测试的文章,但少有C++/CLI项目在不同runtime下的测试对比。
测试代码在C++/CLI项目中,运行耗时计算使用

// 示例伪代码
#include 
auto tp1 = chrono::high_resolution_clock::now();
// code
auto tp2 = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast(tp2 - tp1).count();

C++11 chrono头文件提供了3个标准时钟可以用来计时:

  1. system_clock - 系统提供的实时时钟
  2. high_resolution_clock - 当前系统时钟周期最短的时钟
  3. steady_clock - 不会被调整的单调时钟

测出来的结果让我大跌眼镜。针对C++/CLI项目代码,.NET FW 4.8的性能大概是.NET 5.0的3-10倍(测得比较随意,都是些简单代码重复执行)。一开始我以为是迁移后的C++/CLI项目需要其他设置,所以花了一天时间各种配置调优,结果变化不大。
我尝试一行行代码做性能对比,发现性能差异主要提现在Marshal::StructureToPtrMarshal::PtrToStructure这两个方法上。
就在我心灰意冷,准备回到.NET FW 4.8怀抱的时候,我查到了这个。

Github dotnet 5.0 Marshal.PtrToStructure is slower 8x than netcore3.1 #45100
截止到2021-12-08,.NET6已经发布,这个issue还没有被解决。这个已经被推迟到.NET7的进度里了。而让人难过的是,.NET8才是LTS。

这个github的issue和的我情况差不多,虽然他并不是C++/CLI项目,但也能说明一定的问题。即.NET 5.0里的Marshal::PtrToStructure性能有问题,至少比.NET Core 3.1差了很多。
受到启发,我把C++/CLI项目runtime改成了.NET Core 3.1并与.NET FW 4.8做比较测试。测下来,基本上性能差不多,.NET Core 3.1.NET FW 4.8有略微的优势。至少这给了我信心,可以暂时先迁往3.1,等时机成熟,再前往LTS版的.NET 6.0

总结

本篇文章遇到的问题,严格意义上,不能算是C++/CLI项目的问题,但由于C++/CLI项目用到Marshal::StructureToPtrMarshal::PtrToStructure的场景比较多,所以受到了影响。但寻根溯源,这还是.NET 5.0的问题。毕竟是新版本,一不小心就踩坑翻车了。本文也是提醒大家,如果需要在.NET 5.0中使用Marshal::StructureToPtrMarshal::PtrToStructure等方法,务必小心谨慎。

更新

20230803

针对Marshal::PtrToStructure这个操作,主要用于将native类型转化为managed类型,循环操作10000次。

运行版本 dll编译版本 平均单次耗时
nfx 4.8 nfx 4.8 约1.28us-1.35us
netcore 3.1 netcore 3.1 约1.13us-1.162us
net 6.0 netcore 3.1 约4.50us-4.89us
net 7.0 netcore 3.1 约5.13us-7.27us
netcore 3.1 net 6.0 不支持
net 6.0 net 6.0 约4.58us-4.99us
net 7.0 net 6.0 约5.44us-5.86us
netcore 3.1 net 7.0 不支持
net 6.0 net 7.0 不支持
net 7.0 net 7.0 约5.41us-6.11us

由以上粗略的测试可知,Marshal::PtrToStructure的性能,在nfx4.8netcore3.1上基本一致。随着.NET版本提升,性能持续下降。在net7.0上降低比较显著。考虑到该版本为过渡版本,可以忽略。该问题在GitHub上的相关issue仍然没有解决。考虑到netnfx版本逐渐脱节,且C#新版本特性很吸引人。nfx4.8目前只支持到C#8.0net6.0支持最新的C#11
考虑迁移,暂且忍受这块的性能损耗。期待未来版本修复。

你可能感兴趣的:(C#/.NET,性能分析,部署与配置,.net,c++,microsoft)