C#编译器输出与C++/CLI编译器的区别

Mau*_*ves 14 c# testing performance c++-cli visual-c++

我有一个WPF应用程序,可以在大型数据集中进行大量匹配,目前它使用C#和LINQ来匹配POCO并在网格中显示.随着所包含数据集的数量增加,数据量增加,我被要求查看性能问题.我今天晚上测试的一个假设是,如果我们将一些代码转换为C++ CLI,是否存在实质性差异.为此,我编写了一个简单的测试,创建了List<>5,000,000个项目,然后做了一些简单的匹配.基本对象结构是:

public class CsClassWithProps
{
    public CsClassWithProps()
    {
        CreateDate = DateTime.Now;
    }

    public long Id { get; set; }
    public string Name { get; set; }
    public DateTime CreateDate { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我注意到的一件事是,平均而言,对于创建列表的简单测试,然后构建具有偶数ID的所有对象的子列表,C++/CLI代码在我的开发机器上慢了大约8%(64位Win8) ,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);
}
Run Code Online (Sandbox Code Playgroud)

C#类在上面.C++类定义为:

public ref class CppClassWithMembers
{
public:
    long long Id;
    System::DateTime CreateDateTime;
    System::String^ Name;

    CppClassWithMembers()
    {
        this->CreateDateTime = System::DateTime::Now;
    }
};
Run Code Online (Sandbox Code Playgroud)

当我为两个类的构造函数提取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
Run Code Online (Sandbox Code Playgroud)

然后是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
Run Code Online (Sandbox Code Playgroud)

我的问题是:为什么C++代码使用local来存储调用的值DateTime.Now?是否存在C++特定的原因,或者它们是如何选择实现编译器的?

我知道已经有很多其他的方法来提高性能,我知道我很远了兔子洞,因为它是,但我很好奇,想知道如果任何人都可以在此提供一些线索.自从我完成C++以来已经有很长一段时间了,随着Windows 8的出现,以及微软重新关注C++,我认为刷新会很好,这也是我锻炼动机的一部分,但是两个编译器输出之间的差异引起了我的注意.

Han*_*ant 6

System::DateTime CreateDateTime;
Run Code Online (Sandbox Code Playgroud)

这听起来像一个技巧问题.你发布的片段肯定不会生成你发布的IL.您对CreateDateTime成员的实际声明是:

System::DateTime^ CreateDateTime;
Run Code Online (Sandbox Code Playgroud)

在您发布的IL中清晰可见.它生成了拳击转换,将值类型值转换为引用对象.这是C++/CLI中一个非常常见的错误,很容易意外地输入帽子.一个编译器真的应该生成警告,但不是.是的,它使代码陷入困境,拳击转换不是免费的.

您尝试使用C++/CLI加速代码是一个失败的原因.只要您在C++/CLI中编写托管代码,您将获得与C#编译器生成的IL相同的IL.C++/CLI的价值在于能够非常轻松且廉价地调用非托管代码.然而,使用这样的代码不太可能产生良好的结果.您调用的非托管代码必须是"实质性的",以便从托管代码执行切换到非托管代码执行所产生的代价可以忽略不计.对于不需要任何数据转换的简单转换,该成本在少数CPU周期之间徘徊.当您需要执行诸如引脚数组或转换字符串之类的操作时,需要数百个周期.