我有一个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/

10-11 22:45
查看更多