c# - C# 编译器和 C++/CLI 编译器的输出之间的差异

标签 c# performance visual-c++ testing c++-cli

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

我注意到的一件事是,平均而言,对于创建列表然后构建具有偶数 ID 的所有对象的子列表的简单测试,C++/CLI 代码在我的开发机器上慢了大约 8% (64 位 Win8、8GB 内存)。例如,创建和过滤 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的出现,微软又重新关注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/

相关文章:

c++ - 从像素获取 RGB 值并将 RGB 值设置回同一像素

c++ - 在其他实现文件中包含实现文件有什么用?

c++ - 从 unique_ptr<T> 的 void* 转换为 T** 是如何工作的?

c# - 不继承属性的接口(interface)代理

jQuery 单击处理程序性能 - 附加到类或每个元素

c# - 从进程内存中读取 int

c - 使用 AVX 的平铺矩阵乘法

javascript - 通过 Javascript/PHP 更快地阅读 RSS

c# - 使用 FluentAssertions API 4.x 语法迁移 xunit 项目以使用 FluentAssertions v5.x 版本运行

c# - 在几何体上创建等边三角形网格