C# Reflection IL - Understanding how values are copied
Asked Answered
H

2

0

I'm trying to improve the performance of a certain part of my program which involves deep cloning the same object graph over and over across multiple threads. Currently I use serialization which is a nice easy implementation but I'd like something faster. I came across the idea of IL cloning and am trying to work with some code found here (Whizzo's Blog).

I don't really get IL as yet, so I'm hoping someone can help a little bit and explain some of the stuff to me (I imagine this is the first question of several).

The question here (and b.t.w if anyone has any good links explaining opcodes and reflection.emit a bit more that would be great, MSDN doesn't give a lot of detail) is how are the values copied? I can see that a new object is constructed and popped from the stack

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

Then a little bit later given a field value of interest, the value is somehow copied. I don't understand how we go back to the original object and grab it's value when the original object doesn't seem to be referenced? Or is this some magic of the LocalBuilder (I'm not 100% sure what it does):

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

I've modified the code slightly as I always want a Deep clone and I want it based on serialized fields:

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);
  }
Hoebart answered 16/3, 2012 at 19:57 Comment(10)
What the opcodes do is described in the CLR ECMA specification, but if you are doing stuff like this I highly recommend getting Serge Lidin's latest book on MSIL assembler.Cleptomania
Can you express what you are trying to do in C#?Pooka
“deep cloning the same object graph over and over” Do you really need to do that? Can't you somehow work around that, instead of trying to make it faster?Kwon
@the coop: basically copying all values on some objects recursively, but reflection would be too slow.Hoebart
Can you describe why you're trying to do this in the first place? That is, why clone the object? If multiple threads need to read it, make it threadsafe for reading. If multiple threads need to write it, you might do better with a functional-style "persistent" immutable object rather than cloning a mutable object.Rafaelrafaela
@EricLippert: I tried to reply to svick earlier, but my tablet wasn't having any of it. The reason is multi-threaded writes. I'm trying to write an API, think TSP, where I have Nodes/Resources that have local state (VisitedState, CurrentStock etc.) that get modified during an attempted solution. I'd like to make them immutable, but that adds more state into the actual algorithm, making it harder to extend. Maybe that's the route I should take (I should probably find someone to bounce my design ideas off!).Hoebart
@Ian: Indeed, I am a big fan of immutable data structures to solve problems like this. I did a series of articles a while back on using immutable data structures to do simple graph colourizations: blogs.msdn.com/b/ericlippert/archive/tags/graph+colouringRafaelrafaela
@EricLippert: Thanks Eric, I managed to miss that series (I do try to follow your blog which is excellent), I'll have a read and I'm sure I'll learn a thing or two... (or three!)Hoebart
@Ian: The irony there of course is that in that series I do essentially clone the state rather than using a persistent data structure. The particular problem I wanted to solve -- namely, sudoku puzzles -- requires so little state that it was reasonable to clone it. If the problem was much larger then I would have gone with a persistent data structure.Rafaelrafaela
@EricLippert: Every problem is different, and sometimes the rules need to be bent a little! I'll have a look through, hopefully find a way to use immutability, and if not, well I'll learn some IL :)Hoebart
E
3

First you have

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

Your stack contains (cloneVariable, myObject)

enerator.Emit(OpCodes.Ldfld, field);

This one pops object reference, retrieves the value from the field and pushes the value onto the stack

Your stack contains (cloneVariable, myObject.field)

generator.Emit(OpCodes.Stfld, field);

This one pops object reference and value and stores the value in the object's field. The result is equivalent to C#'s

cloneVariable.field = myObject.field
Ethnomusicology answered 16/3, 2012 at 20:6 Comment(2)
Hmm, when did myObject get on the stack? I think that's probably the part I'm missing!Hoebart
ldarg_0 means load method argument 0 onto the stack. You have only one method argument and since the method is static, the index of this argument is 0 (it would be 1 in an instance method since 0 is reserved for this argument in instance methods)Ethnomusicology
M
0

¿Won't be easier to use structs instead of classes and directly marshall the byte array of the type into the new object memory?

I'm not really shure if that can be done straight forward with Marshalling but I am almost-(always keep the door open to run away hehe)-shure it can be done by compiling with /unsafe, casting the pointer to byte* and copy those bytes to the target struct pointer.

[Edit] Forget about the pointers, you can do it without unsafe just by marshalling. Check this post;

How to convert a structure to a byte array in C#?

[Edit2] I have to learn some patience :P your solution is much faster.

Malvern answered 27/10, 2015 at 16:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.