Подтвердить что ты не робот

Различия между выходом компилятора С# и компилятором С++/CLI

У меня есть приложение WPF, которое много подходит для больших наборов данных, и в настоящее время он использует С# и LINQ для соответствия POCOs и отображения в сетке. Поскольку количество включенных наборов данных увеличилось, а объем данных увеличился, меня попросили рассмотреть проблемы с производительностью. Одно из предположений, которые я тестировал сегодня вечером, заключалось в том, есть ли существенная разница, если мы должны конвертировать часть кода в С++ 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; }
}

Одна вещь, которую я заметил, заключалась в том, что в среднем для простого теста создания списка, а затем для создания под-списка всех объектов с четным идентификатором код С++/CLI был примерно на 8% медленнее на моей машине разработки (64-битная Win8, 8 ГБ оперативной памяти). Например, случай создания и фильтрации объекта С# занимает ~ 7 секунд, тогда как код С++/CLI в среднем составлял ~ 8 секунд. Любопытно, почему это было бы, я использовал ILDASM, чтобы увидеть, что происходит под обложками, и был удивлен, увидев, что код С++/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);
}

Класс С# выше. Класс С++ определяется как:

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

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

Когда я извлекаю IL для обоих конструкторов классов, это то, что я получаю. Сначала С#:

.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

И затем С++:

.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

Мой вопрос: почему код С++ использует локальный для хранения значения вызова из DateTime.Now? Существует ли для С++ причина для этого, или это только то, как они решили реализовать компилятор?

Я уже знаю, что есть много других способов улучшить производительность, и я знаю, что я довольно далеко от кроличьей дыры, как есть, но мне было любопытно узнать, может ли кто-нибудь пролить свет на это. Прошло много времени с тех пор, как я сделал С++, и с появлением Windows 8, и Microsoft вновь сосредоточился на С++, я подумал, что было бы хорошо обновить, и это также было частью моей мотивации для этого упражнения, но разница между двумя выходами компилятора попала мне в глаза.

4b9b3361

Ответ 1

System::DateTime CreateDateTime;

Это звучит как трюк. IL, вы опубликовали, скорее всего, не будет генерироваться фрагментом, который вы опубликовали. Ваше фактическое объявление члена CreateDateTime было:

System::DateTime^ CreateDateTime;

Четко видно в опубликованном вами IL. Он создал преобразование бокса, чтобы преобразовать значение типа значения в ссылочный объект. Это очень распространенная ошибка в С++/CLI, слишком простая, чтобы случайно набрать шляпу. Тот факт, что компилятор действительно должен генерировать предупреждение, но не делает этого. И да, это болотный код вниз, конверсия бокса не приходит бесплатно.

Ваша попытка ускорить работу кода с помощью С++/CLI в противном случае является потерянной причиной. Пока вы пишете управляемый код в С++/CLI, вы получите тот же IL, что и компилятор С#. Значение С++/CLI - это его способность очень легко и дешево вызывать неуправляемый код. Это, однако, вряд ли принесет хорошие результаты либо с таким кодом. Неуправляемый код, который вы вызываете, должен быть "существенным", так что штраф, который вы понесете от перехода от управляемого к неуправляемому исполнению кода, ничтожно мал. Эта стоимость колеблется между несколькими циклами процессора для простого перехода, который не требует преобразования данных. В сотни циклов, когда вам нужно делать что-то вроде pin-массивов или преобразовывать строки.

Ответ 2

Версия С++, которая ближе к тому, что делает компилятор С# (и избавляется от дорогого box), будет следующим:

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

    CppClassWithMembers() : CreateDateTime(System::DateTime::Now) { }
};