繁体   English   中英

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

[英]Field getter using Reflection.Emit Opcodes - usage of IL local variables

我正在学习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与往常一样有用)。 我可以替换PARTs 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现在有了字符串属性,所以我可以摆脱装箱的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();
    }

你可以做的是在第一次使用任何给定的FieldInfo创建一个专门的访问器函数。 这消除了后续访问的反射成本,并用委托调用的成本替换它们,后者要便宜得多。

它只比[反射]快约2倍

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM