我正在学习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)
它工作正常。请注意,这次我没有使用任何局部变量,f
和t
是来自方法外部的变量。但是,使用这种方法,我需要生成尽可能多的方法,例如它所使用的类型和字段的数量。所以这是非常令人不满意的解决方案。
显然,我对局部变量做错了,但我无法弄清楚究竟是什么。我错过了什么?
修改
这是大简化后的代码。 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();
}
答案 0 :(得分:3)
您可以做的是在首次使用任何给定的FieldInfo
时创建一个专门的访问器函数。这样可以消除后续访问的反射成本,并用委托调用的成本替换它们,这种成本要低得多。
它只比[反射]快2倍
我怀疑这个基准测试结果。 如何更快?如果在IL中生成与C#编译器生成的相同的代码,您将获得相同的性能级别。没有必要这样做。