想调用一个方法很容易,直接代码调用就行,这人人都会。其次呢,还可以使用反射。不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样。虽然这是个众所周知的现象,我们还是来写个程序来验证一下。比如我们现在新建一个Console应用程序,编写一个最简单的Call方法。
复制代码 代码如下:
class Program
{
static void Main(string[] args)
{
}
public void Call(object o1, object o2, object o3) { }
}
Call方法接受三个object参数却没有任何实现,这样我们就可以让测试专注于方法调用,而并非方法实现本身。于是我们开始编写测试代码,比较一下方法的直接调用与反射调用的性能差距:
复制代码 代码如下:
static void Main(string[] args)
{
int times = 1000000;
Program program = new Program();
object[] parameters = new object[] { new object(), new object(), new object() };
program.Call(null, null, null); // force JIT-compile
Stopwatch watch1 = new Stopwatch();
watch1.Start();
for (int i = 0; i < times; i++)
{
program.Call(parameters[0], parameters[1], parameters[2]);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Directly invoke)");
MethodInfo methodInfo = typeof(Program).GetMethod("Call");
Stopwatch watch2 = new Stopwatch();
watch2.Start();
for (int i = 0; i < times; i++)
{
methodInfo.Invoke(program, parameters);
}
watch2.Stop();
Console.WriteLine(watch2.Elapsed + " (Reflection invoke)");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
执行结果如下:
复制代码 代码如下:
00:00:00.0119041 (Directly invoke)
00:00:04.5527141 (Reflection invoke)
Press any key to continue...
通过各调用一百万次所花时间来看,两者在性能上具有数量级的差距。因此,很多框架在必须利用到反射的场景中,都会设法使用一些较高级的替代方案来改善性能。例如,使用CodeDom生成代码并动态编译,或者使用Emit来直接编写IL。不过自从.NET 3.5发布了expression相关的新特性,我们在以上的情况下又有了更方便并直观的解决方案。
了解expression相关特性的朋友可能知道,System.Linq.expressions.expression
复制代码 代码如下:
public Func
Action
public Func
{
expression
((Program)instance).Call(parameters[0], parameters[1]);
return exp.Compile();
}
至此,我想朋友们也已经能够轻松得出调用静态方法的委托构造方式了。可见,这个解决方案的关键在于构造一个合适的expression
复制代码 代码如下:
public class DynamicMethodExecutor
{
private Func
public DynamicMethodExecutor(MethodInfo methodInfo)
{
this.m_execute = this.GetExecuteDelegate(methodInfo);
}
public object Execute(object instance, object[] parameters)
{
return this.m_execute(instance, parameters);
}
private Func
{
// parameters to execute
Parameterexpression instanceParameter =
expression.Parameter(typeof(object), "instance");
Parameterexpression parametersParameter =
expression.Parameter(typeof(object[]), "parameters");
// build parameter list
List
ParameterInfo[] paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
// (Ti)parameters[i]
Binaryexpression valueObj = expression.ArrayIndex(
parametersParameter, expression.Constant(i));
Unaryexpression valueCast = expression.Convert(
valueObj, paramInfos[i].ParameterType);
parameterexpressions.Add(valueCast);
}
// non-instance for static method, or ((TInstance)instance)
expression instanceCast = methodInfo.IsStatic ? null :
expression.Convert(instanceParameter, methodInfo.ReflectedType);
// static invoke or ((TInstance)instance).Method
MethodCallexpression methodCall = expression.Call(
instanceCast, methodInfo, parameterexpressions);
// ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
if (methodCall.Type == typeof(void))
{
expression> lambda =
expression.Lambda>(
methodCall, instanceParameter, parametersParameter);
Action
return (instance, parameters) =>
{
execute(instance, parameters);
return null;
};
}
else
{
Unaryexpression castMethodCall = expression.Convert(
methodCall, typeof(object));
expression
expression.Lambda
castMethodCall, instanceParameter, parametersParameter);
return lambda.Compile();
}
}
}
DynamicMethodExecutor的关键就在于GetExecuteDelegate方法中构造expression Tree的逻辑。如果您对于一个expression Tree的结构不太了解的话,不妨尝试一下使用expression Tree Visualizer 来对一个现成的expression Tree进行观察和分析。我们将一个MethodInfo对象传入DynamicMethodExecutor的构造函数之后,就能将各组不同的实例对象和参数对象数组传入Execute进行执行。这一切就像使用反射来进行调用一般,不过它的性能就有了明显的提高。例如我们添加更多的测试代码:
复制代码 代码如下:
DynamicMethodExecutor executor = new DynamicMethodExecutor(methodInfo);
Stopwatch watch3 = new Stopwatch();
watch3.Start();
for (int i = 0; i < times; i++)
{
executor.Execute(program, parameters);
}
watch3.Stop();
Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");
现在的执行结果则是:
复制代码 代码如下:
00:00:00.0125539 (Directly invoke)
00:00:04.5349626 (Reflection invoke)
00:00:00.0322555 (Dynamic executor)
Press any key to continue...
事实上,expression
补充
木野狐兄在评论中引用了Code Project的文章《A General Fast Method Invoker》,其中通过Emit构建了FastInvokeHandler委托对象(其签名与Func
1.范型委托类型的执行性能较非范型委托类型略低(求证)。
2.多了一次Execute方法调用,损失部分性能。
3.生成的IL代码更为短小紧凑。
4.木野狐兄没有使用Release模式编译。:P
不知道是否有对此感兴趣的朋友能够再做一个测试,不过请注意此类性能测试一定需要在Release编译下进行(这点很容易被忽视),否则意义其实不大。
此外,我还想强调的就是,本篇文章进行是纯技术上的比较,并非在引导大家追求点滴性能上的优化。有时候看到一些关于比较for或foreach性能优劣的文章让许多朋友都纠结与此,甚至搞得面红耳赤,我总会觉得有些无可奈何。其实从理论上来说,提高性能的方式有许许多多,记得当时在大学里学习Introduction to Computer System这门课时得一个作业就是为一段C程序作性能优化,当时用到不少手段,例如内联方法调用以减少CPU指令调用次数、调整循环嵌套顺序以提高CPU缓存命中率,将一些代码使用内嵌ASM替换等等,可谓“无所不用其极”,大家都在为几个时钟周期的性能提高而发奋图强欢呼雀跃……
那是理论,是在学习。但是在实际运用中,我们还必须正确对待学到的理论知识。我经常说的一句话是:“任何应用程序都会有其性能瓶颈,只有从性能瓶颈着手才能做到事半功倍的结果。”例如,普通Web应用的性能瓶颈往往在外部IO(尤其是数据库读写),要真正提高性能必须从此入手(例如数据库调优,更好的缓存设计)。正因如此,开发一个高性能的Web应用程序的关键不会在语言或语言运行环境上,.NET、RoR、PHP、Java等等在这一领域都表现良好。



