Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences Between Output of C# Compiler and C++/CLI Compiler

I have a WPF application that does a lot of matching across large datasets, and currently it uses C# and LINQ to match POCOs and display in a grid. As the number of datasets included has increased, and the volume of data has increased, I've been asked to look at performance issues. One of the assumptions that I was testing this evening was whether there's a substantive difference if we were to convert some of the code to C++ CLI. To that end I wrote a simple test that creates a List<> with 5,000,000 items, and then does some simple matching. The basic object structure is:

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

    public long Id { get; set; }
    public string Name { get; set; }
    public DateTime CreateDate { get; set; }
}

One thing that I noticed was that on average, for the simple test of creating the list and then building a sub-list of all objects with an even ID, the C++/CLI code was about 8% slower on my development machine (64bit Win8, 8GB of RAM). For example, the case of a C# object being created and filtered took ~7 seconds, while the C++/CLI code took ~8 seconds on average. Curious as to why this would be, I used ILDASM to see what was happening under the covers, and was surprised to see that the C++/CLI code has extra steps in the constructor. First the test code:

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);
}

The C# class is above. The C++ class is defined as:

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

    CppClassWithMembers()
    {
        this->CreateDateTime = System::DateTime::Now;
    }
};

When I extract the IL for both classes' constructors, this is what I get. First the 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

And then the 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

My question is: why is the C++ code using the local to store the value of the call from DateTime.Now? Is there a C++-specific reason for this to happen, or is it just how they chose to implement the compiler?

I know already that there are many other ways to improve performance, and I know that I'm pretty far down the rabbit hole as it is, but I was curious to know if anyone could shed some light on this. It's been a long time since I've done C++, and with the advent of Windows 8, and Microsoft's renewed focus on C++, I thought it would be good to refresh, and that was also part of my motivation for this exercise, but the difference between the two compiler outputs caught my eye.

like image 802
Maurice Reeves Avatar asked Dec 25 '12 04:12

Maurice Reeves


2 Answers

System::DateTime CreateDateTime;

This sounds like a trick question. The IL you posted most certainly won't be generated by the snippet you posted. Your actual declaration of the CreateDateTime member was:

System::DateTime^ CreateDateTime;

Clearly visible in the IL you posted. It produced the boxing conversion to convert the value type value to a reference object. This is a very common mistake in C++/CLI, much too easy to accidentally type the hat. One that the compiler really ought to generate a warning for, but doesn't. And yes, it bogs code down, the boxing conversion doesn't come for free.

Your attempt to speed code up by using C++/CLI is otherwise a lost cause. As long as you write managed code in C++/CLI, you'll get the same kind of IL that the C# compiler generates. The value of C++/CLI is its ability to very easily and cheaply call unmanaged code. That is however unlikely to produce good results either with code like this. The unmanaged code you call must be "substantive" so that the penalty you incur from switching from managed to unmanaged code execution is negligible. That cost hovers between a handful of CPU cycles for a simple transition that doesn't need any data conversion. To hundreds of cycles when you need to do things like pin arrays or convert strings.

like image 87
Hans Passant Avatar answered Oct 23 '22 23:10

Hans Passant


A C++ version that is closer to what the C# compiler does (and gets rid of the expensive box) would be this:

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

    CppClassWithMembers() : CreateDateTime(System::DateTime::Now) { }
};
like image 27
Tilo Avatar answered Oct 23 '22 23:10

Tilo