使用 Reflection.Emit Opcode 的 Field getter - IL 局部变量的使用

本文关键字:IL 局部变量 Field Reflection Emit Opcode 使用 getter | 更新日期: 2023-09-27 17:56:27

我正在学习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();
    }
}

此代码崩溃(在调用时,Emit 中的异常一如既往地有用)。我可以更换零件 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();
    }

使用 Reflection.Emit Opcode 的 Field getter - IL 局部变量的使用

您可以做的是在第一次使用任何给定FieldInfo时创建一个专门的访问器函数。这消除了后续访问的反射成本,并将其替换为更便宜的委托调用成本。

它只比[反射]快2倍左右

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