C# Reflection IL — понимание того, как значения копируются

Я пытаюсь улучшить производительность определенной части моей программы, которая включает в себя глубокое клонирование одного и того же графа объектов снова и снова в нескольких потоках. В настоящее время я использую сериализацию, которая является хорошей и простой реализацией, но мне бы хотелось чего-то более быстрого. Я столкнулся с идеей клонирования IL и пытаюсь работать с кодом, найденным здесь (блог Whizzo).

На самом деле я еще не понимаю IL, поэтому я надеюсь, что кто-то может немного помочь и объяснить мне некоторые вещи (я думаю, это первый вопрос из нескольких).

Вопрос здесь (и, кстати, если у кого-то есть хорошие ссылки, объясняющие коды операций и Reflection.emit немного больше, что было бы здорово, MSDN не дает много подробностей) заключается в том, как копируются значения? Я вижу, что новый объект создается и извлекается из стека.

generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc, cloneVariable);

Затем, немного позже, учитывая интересующее значение поля, это значение каким-то образом копируется. Я не понимаю, как мы возвращаемся к исходному объекту и получаем его значение, когда кажется, что исходный объект не упоминается? Или это какая-то магия LocalBuilder (я не уверен на 100%, что он делает):

// I *THINK* this Pushes the cloneVariable on the stack, loads an argument (from where?) and sets the field value based on the FieldInfo??
generator.Emit(OpCodes.Ldloc, cloneVariable);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Stfld, field);

Я немного изменил код, так как мне всегда нужен глубокий клон, и я хочу, чтобы он основывался на сериализованных полях:

private static T CloneObjectWithILDeep(T myObject)
{
   Delegate myExec = null;
   if (!_cachedILDeep.TryGetValue(typeof(T), out myExec))
   {
     // Create ILGenerator            
     DynamicMethod dymMethod = new DynamicMethod("DoDeepClone", typeof(T), new Type[] { typeof(T) }, Assembly.GetExecutingAssembly().ManifestModule, true);
     ILGenerator generator = dymMethod.GetILGenerator();
     LocalBuilder cloneVariable = generator.DeclareLocal(myObject.GetType());

     ConstructorInfo cInfo = myObject.GetType().GetConstructor(Type.EmptyTypes);
     generator.Emit(OpCodes.Newobj, cInfo);
     generator.Emit(OpCodes.Stloc, cloneVariable);

     foreach (FieldInfo field in typeof(T).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))
     {         
        if(field.IsNotSerialized)
            continue;

        if (field.FieldType.IsValueType || field.FieldType == typeof(string))
        {
           generator.Emit(OpCodes.Ldloc, cloneVariable);
           generator.Emit(OpCodes.Ldarg_0);
           generator.Emit(OpCodes.Ldfld, field);
           generator.Emit(OpCodes.Stfld, field);
         }
         else if (field.FieldType.IsClass)
         {
           CopyReferenceType(generator, cloneVariable, field);
         }
       }

      generator.Emit(OpCodes.Ldloc_0);
      generator.Emit(OpCodes.Ret);
      myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));
      _cachedILDeep.Add(typeof(T), myExec);
    }
    return ((Func<T, T>)myExec)(myObject);
  }

person Ian    schedule 16.03.2012    source источник
comment
То, что делают коды операций, описано в спецификации CLR ECMA, но если вы делаете что-то подобное, я настоятельно рекомендую получить последнюю книгу Сержа Лидина по ассемблеру MSIL.   -  person 500 - Internal Server Error    schedule 17.03.2012
comment
Можете ли вы выразить то, что вы пытаетесь сделать на C#?   -  person thecoop    schedule 17.03.2012
comment
«глубокое клонирование одного и того же графа объектов снова и снова» Вам действительно нужно это делать? Разве вы не можете как-то обойти это, вместо того, чтобы пытаться сделать это быстрее?   -  person svick    schedule 17.03.2012
comment
@the coop: в основном рекурсивное копирование всех значений некоторых объектов, но отражение было бы слишком медленным.   -  person Ian    schedule 17.03.2012
comment
Можете ли вы описать, почему вы пытаетесь сделать это в первую очередь? То есть зачем клонировать объект? Если несколько потоков должны прочитать его, сделайте его потокобезопасным для чтения. Если несколько потоков должны написать его, вам может быть лучше с постоянным неизменяемым объектом в функциональном стиле, а не с клонированием изменяемого объекта.   -  person Eric Lippert    schedule 17.03.2012
comment
@EricLippert: я пытался ответить svick ранее, но на моем планшете ничего этого не было. Причина - многопоточная запись. Я пытаюсь написать API, думаю, TSP, где у меня есть узлы/ресурсы, которые локальное состояние (VisitedState, CurrentStock и т. д.), которое изменяется во время попытки решения. Я хотел бы сделать их неизменяемыми, но это добавляет больше состояния в фактический алгоритм, что затрудняет его расширение. Может быть, мне следует пойти по этому пути (вероятно, мне следует найти кого-то, кто оттолкнет меня от моих дизайнерских идей!).   -  person Ian    schedule 17.03.2012
comment
@Ian: Действительно, я большой поклонник неизменяемых структур данных для решения подобных проблем. Некоторое время назад я написал серию статей об использовании неизменяемых структур данных для простой раскраски графов: blogs.msdn.com/b/ericlippert/archive/tags/graph+coloring   -  person Eric Lippert    schedule 17.03.2012
comment
@EricLippert: Спасибо, Эрик, мне удалось пропустить эту серию (я стараюсь следить за вашим блогом, который превосходен), я почитаю, и я уверен, что узнаю кое-что... (или три !)   -  person Ian    schedule 17.03.2012
comment
@Ian: Ирония, конечно, в том, что в этой серии я делаю по существу клонирование состояния, а не использование постоянной структуры данных. Конкретная проблема, которую я хотел решить, а именно, головоломки судоку, требует так мало состояния, что было разумно ее клонировать. Если бы проблема была намного больше, я бы выбрал постоянную структуру данных.   -  person Eric Lippert    schedule 17.03.2012
comment
@EricLippert: Каждая проблема уникальна, и иногда правила нужно немного изменить! Я посмотрю, надеюсь, найду способ использовать неизменность, а если нет, то я изучу IL :)   -  person Ian    schedule 17.03.2012


Ответы (2)


Сначала у вас есть

generator.Emit(OpCodes.Ldloc, cloneVariable);
generator.Emit(OpCodes.Ldarg_0);

Ваш стек содержит (cloneVariable, myObject)

enerator.Emit(OpCodes.Ldfld, field);

Этот извлекает ссылку на объект, извлекает значение из поля и помещает значение в стек.

Ваш стек содержит (cloneVariable, myObject.field)

generator.Emit(OpCodes.Stfld, field);

Этот извлекает ссылку на объект и значение и сохраняет значение в поле объекта. Результат эквивалентен C#

cloneVariable.field = myObject.field
person Wiktor Zychla    schedule 16.03.2012
comment
Хм, а когда myObject попал в стек? Я думаю, что это, вероятно, та часть, которую мне не хватает! - person Ian; 17.03.2012
comment
ldarg_0 означает загрузку аргумента метода 0 в стек. У вас есть только один аргумент метода, и поскольку метод равен static, индекс этого аргумента равен 0 (в методе экземпляра он будет равен 1, поскольку 0 зарезервирован для аргумента this в методах экземпляра). - person Wiktor Zychla; 17.03.2012

¿Не будет ли проще использовать структуры вместо классов и напрямую маршалировать массив байтов типа в новую объектную память?

Я не совсем уверен, можно ли это сделать напрямую с помощью Marshalling, но я почти (всегда держу дверь открытой, чтобы убежать, хе-хе) уверен, что это можно сделать, скомпилировав с /unsafe, приведя указатель к byte* и скопируйте эти байты в указатель целевой структуры.

[Изменить] Забудьте об указателях, вы можете сделать это без риска, просто упорядочив. Проверьте этот пост;

Как преобразовать структуру в массив байтов в С#?

[Edit2] Мне нужно научиться терпению: P ваше решение намного быстрее.

person Aridane Álamo Morera    schedule 27.10.2015