Field getter使用Reflection.Emit操作码 - 使用IL局部变量

时间:2014-04-03 10:03:42

标签: c# exception reflection.emit local-variables intermediate-language

我正在学习IL,我想写一种高性能黑客来访问任何对象的字段值(比如反射但速度更快)。

所以我做了这个课程进行测试:

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

然后我有这样一个程序(我在所有IL指令之后添加了注释以提高可读性,还分成了几个逻辑部分;在每个部分之后都写了我认为现在在堆栈上的内容):

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

此代码崩溃(在Invoke上,Emit中的Exception与往常一样有用)。我可以替换第2部分和第3部分,如下所示:

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

它工作正常。请注意,这次我没有使用任何局部变量,ft是来自方法外部的变量。但是,使用这种方法,我需要生成尽可能多的方法,例如它所使用的类型和字段的数量。所以这是非常令人不满意的解决方案。

显然,我对局部变量做错了,但我无法弄清楚究竟是什么。我错过了什么?


修改

这是大简化后的代码。 CrashTestDummy现在有了string属性,所以我可以摆脱装箱的int:

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

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

主要代码如下:

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

1 个答案:

答案 0 :(得分:3)

您可以做的是在首次使用任何给定的FieldInfo时创建一个专门的访问器函数。这样可以消除后续访问的反射成本,并用委托调用的成本替换它们,这种成本要低得多。

  

它只比[反射]快2倍

我怀疑这个基准测试结果。 如何更快?如果在IL中生成与C#编译器生成的相同的代码,您将获得相同的性能级别。没有必要这样做。