Question

I'm learning IL and I thought of writing kind of a high-performance hack to access a field values of any object (like a reflection but faster).

So I made this class for testing:

public class CrashTestDummy
{
    public int Number { get; set; }

    public CrashTestDummy(int number)
    {
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("CrashTestDummy: Number = {0}", Number);
    }
}

Then I have such a program (I added comments after all IL instructions to improve readability, also divided into several logical parts; after every part there is written what I think is now on the stack):

class Program
{
    static void Main(string[] args)
    {
        var backingFieldFormat = "<{0}>k__BackingField";
        var getPropFormat = "get_{0}";

        var dummy = new CrashTestDummy(5);

        var t = dummy.GetType();
        var f = t.GetField(string.Format(backingFieldFormat, "Number"),
            BindingFlags.Instance | BindingFlags.NonPublic); 

        // define method: object Getter(Type, FieldInfo, Object), ignoring private fields.
        var getter = new DynamicMethod("Getter", typeof(object), new Type[] { typeof(Type), typeof(FieldInfo), typeof(object) }, true);
        var il = getter.GetILGenerator();
        var _t = il.DeclareLocal(typeof(Type));      // Type _t;
        var _f = il.DeclareLocal(typeof(FieldInfo)); // FieldInfo _f;
        var _ft = il.DeclareLocal(typeof(Type));     // Type _ft;
        var get_FieldType = typeof(FieldInfo).GetMethod(string.Format(getPropFormat, "FieldType")); // MethodInfo for FieldInfo.FieldType getter
        var get_IsValueType = typeof(Type).GetMethod(string.Format(getPropFormat, "IsValueType"));  // MethodInfo for Type.IsValueType getter
        var lbl_NotValueType = il.DefineLabel();       // label "NotValueType"

        // PART 1.
        il.Emit(OpCodes.Ldarg_0);                      // Get argument 0 (type of object) ...
        il.Emit(OpCodes.Castclass, typeof(Type));      // ... cast it to Type (just in case) ...
        il.Emit(OpCodes.Stloc, _t);                    // ... and assign it to _t.
        il.Emit(OpCodes.Ldarg_1);                      // Get argument 1 (desired field of object) ...
        il.Emit(OpCodes.Castclass, typeof(FieldInfo)); // ... cast it to FieldInfo (just in case) ...
        il.Emit(OpCodes.Stloc, _f);                    // ... and assign it to _f.
        // stack is empty

        // DEBUG PART
        il.EmitWriteLine(_t);           // these two lines show that both
        il.EmitWriteLine(t.ToString()); // t and _t contains equal Type
        il.EmitWriteLine(_f);           // these two lines show that both
        il.EmitWriteLine(f.ToString()); // f and _f contains equal FieldInfo
        // stack is empty

        // PART 2.
        il.Emit(OpCodes.Ldarg_2);       // Get argument 2 (object itself) ...
        il.Emit(OpCodes.Castclass, _t); // ... cast it to type of object ...
        il.Emit(OpCodes.Ldfld, _f);     // ... and get it's desired field's value.
        // desired field's value on the stack

        // PART 3.
        il.Emit(OpCodes.Ldloc, _f);                 // Get FieldInfo ...
        il.Emit(OpCodes.Call, get_FieldType);       // ... .FieldType ...
        il.Emit(OpCodes.Call, get_IsValueType);     // ... .IsValueType; ...
        il.Emit(OpCodes.Brfalse, lbl_NotValueType); // ... IF it's false - goto NotValueType.
            il.Emit(OpCodes.Ldloc, _f);             // Get FieldInfo ...
            il.Emit(OpCodes.Call, get_FieldType);   // ... .FieldType ...
            il.Emit(OpCodes.Stloc, _ft);            // ... and assign it to _ft.
            il.Emit(OpCodes.Box, _ft);              // Box field's value of type _ft.
        il.MarkLabel(lbl_NotValueType);             // NotValueType:
        // desired field's value on the stack (boxed, if it's value type)

        // PART 4.
        il.Emit(OpCodes.Ret); // return.            

        var value = getter.Invoke(null, new object[] { t, f, dummy });
        Console.WriteLine(value);
        Console.ReadKey();
    }
}

This code crashes (on Invoke, and Exception from within Emit is as helpful as always). I can replace PARTs 2. and 3. as below:

        // ALTERNATE PART 2.
        il.Emit(OpCodes.Ldarg_2);      // Get argument 2 (object itself) ...
        il.Emit(OpCodes.Castclass, t); // ... cast it to type of object ...
        il.Emit(OpCodes.Ldfld, f);     // ... and get it's desired field's value.
        // desired field's value on the stack

        // ALTERNATE PART 3.
        if (f.FieldType.IsValueType)           
            il.Emit(OpCodes.Box, f.FieldType); // Box field's value of type f.FieldType.
        // desired field's value on the stack (boxed, if it's value type)

and it works fine. Notice that this time I'm not using any local variables, f and t are variables from outside of method. However with this approach I would need to generate as many methods, as number of types and fields it was being used to. So it's pretty unsatisfying solution.

I'm doing something wrong with local variables, apparently, but I was unable to figure out what it is exactly. What am I missing?


Edit:

Here's the code after big simplification. CrashTestDummy has now string property, so I could get rid of boxing the int:

public class CrashTestDummy
{
    public string Text { get; set; }

    public CrashTestDummy(string text)
    {
        Text = text;
    }
}

And the main code is as follows:

    static string BackingField(string propertyName)
    {
        return string.Format("<{0}>k__BackingField", propertyName);
    }

    static void Main(string[] args)
    {
        // INIT
        var dummy = new CrashTestDummy("Loremipsum");
        var t = typeof(CrashTestDummy);
        var f = t.GetField(BackingField("Text"),
            BindingFlags.Instance |
            BindingFlags.Public |
            BindingFlags.NonPublic);

        var fieldGetter = new DynamicMethod("FieldGetter", typeof(object), new Type[] { typeof(object) }, true);
        var il = fieldGetter.GetILGenerator();

        // DYNAMIC METHOD CODE
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Castclass, t);
        il.Emit(OpCodes.Ldfld, f);
        il.Emit(OpCodes.Ret);

        var d = (Func<object, object>)fieldGetter.CreateDelegate(typeof(Func<object, object>));

        // BENCHMARK
        Stopwatch sw = new Stopwatch();
        var len = 1000000;
        for (int i = 0; i < len; i++)
        {
            sw.Start();
            d(dummy);
            sw.Stop();
        }
        Console.WriteLine(sw.Elapsed);
        sw.Reset();
        for (int i = 0; i < len; i++)
        {
            sw.Start();
            f.GetValue(dummy);
            sw.Stop();
        }
        Console.WriteLine(sw.Elapsed);

        Console.ReadKey();
    }
Was it helpful?

Solution

What you could do is create a specialized accessor function on the first use of any given FieldInfo. This removes reflection costs for subsequent accesses and replaces them with the cost of a delegate call which is much cheaper.

It's only about 2x faster than [reflection]

I would doubt this benchmark result. How could it be faster? If you generate the same code in IL that the C# compiler would have generated you will get the same level of performance. No point in doing that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top