我有一个WPF应用程序,它可以对大型数据集进行大量匹配,当前它使用C#和LINQ来匹配POCO并显示在网格中。随着所包含的数据集数量的增加和数据量的增加,我被要求研究性能问题。我今天晚上测试的一个假设是,如果要将某些代码转换为C++ CLI,是否存在实质性差异。为此,我编写了一个简单的测试,该测试创建具有5,000,000个项目的List<>
,然后进行一些简单的匹配。基本的对象结构是:
public class CsClassWithProps
{
public CsClassWithProps()
{
CreateDate = DateTime.Now;
}
public long Id { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get; set; }
}
我注意到的一件事是,平均而言,对于创建列表然后对所有具有偶数ID的对象的子列表进行简单测试,在我的开发机上(64位Win8),C++ / CLI代码的速度慢了大约8%。 ,8GB的RAM)。例如,创建和过滤C#对象的过程大约需要7秒钟,而C++ / CLI代码平均大约需要8秒钟。好奇为什么会这样,我使用ILDASM来了解幕后情况,并且很惊讶地看到C++ / CLI代码在构造函数中有额外的步骤。首先测试代码:
static void CreateCppObjectWithMembers()
{
List<CppClassWithMembers> results = new List<CppClassWithMembers>();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < Iterations; i++)
{
results.Add(new CppClassWithMembers() { Id = i, Name = string.Format("Name {0}", i) });
}
var halfResults = results.Where(x => x.Id % 2 == 0).ToList();
sw.Stop();
Console.WriteLine("Took {0} total seconds to execute", sw.Elapsed.TotalSeconds);
}
C#类在上面。 C++类定义为:
public ref class CppClassWithMembers
{
public:
long long Id;
System::DateTime CreateDateTime;
System::String^ Name;
CppClassWithMembers()
{
this->CreateDateTime = System::DateTime::Now;
}
};
当我提取两个类的构造函数的IL时,这就是我得到的。首先是C#:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 21 (0x15)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldarg.0
IL_0009: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
IL_000e: stfld valuetype [mscorlib]System.DateTime CsLibWithMembers.CsClassWithMembers::CreateDate
IL_0013: nop
IL_0014: ret
} // end of method CsClassWithMembers::.ctor
然后是C++:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 25 (0x19)
.maxstack 2
.locals ([0] valuetype [mscorlib]System.DateTime V_0)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
IL_000b: stloc.0
IL_000c: ldarg.0
IL_000d: ldloc.0
IL_000e: box [mscorlib]System.DateTime
IL_0013: stfld class [mscorlib]System.ValueType modopt([mscorlib]System.DateTime) modopt([mscorlib]System.Runtime.CompilerServices.IsBoxed) CppLibWithMembers.CppClassWithMembers::CreateDateTime
IL_0018: ret
} // end of method CppClassWithMembers::.ctor
我的问题是:为什么C++代码使用本地代码来存储
DateTime.Now
的调用值?是否有特定于C++的原因导致这种情况发生,或者仅仅是他们选择如何实现编译器?我已经知道还有许多其他方法可以提高性能,而且我知道我的想法还很遥远,但是我很想知道是否有人可以对此有所了解。自完成C++以来已经有很长时间了,随着Windows 8的到来以及Microsoft重新关注C++,我认为刷新会很好,这也是我进行此练习的动机之一,但是两个编译器输出之间的差异引起了我的注意。
最佳答案
System::DateTime CreateDateTime;
这听起来像一个技巧问题。您发布的IL肯定不会由您发布的代码段生成。您对CreateDateTime成员的实际声明是:
System::DateTime^ CreateDateTime;
在您发布的IL中清晰可见。它产生了装箱转换,以将值类型值转换为引用对象。这是C++ / CLI中的一个非常常见的错误,太容易导致不小心打入帽子。编译器确实应该为之生成警告,但事实并非如此。是的,它使代码陷入困境,拳击转换并非免费提供。
否则,您尝试使用C++ / CLI加速代码的尝试将是失败的原因。只要您使用C++ / CLI编写托管代码,您将获得与C#编译器生成的相同类型的IL。 C++ / CLI的值(value)在于其能够非常轻松且廉价地调用非托管代码的能力。但是,用这样的代码都不太可能产生良好的结果。您调用的非托管代码必须是“实质性的”,因此从执行托管代码切换到非托管代码所造成的损失可以忽略不计。对于不需要任何数据转换的简单转换,该成本在几个CPU周期之间徘徊。需要执行引脚阵列或转换字符串之类的数百个周期时。
关于c# - C#编译器和C++/CLI编译器的输出之间的差异,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14027580/