方法的直接调用,反射调用与……Lambda表达式调用

2008-11-24 09:59 by Jeffrey Zhao, 32557 阅读, 100 评论, 收藏, 编辑

想调用一个方法很容易,直接代码调用就行,这人人都会。其次呢,还可以使用反射。不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样。虽然这是个众所周知的现象,我们还是来写个程序来验证一下。比如我们现在新建一个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-compileStopwatch 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<TDelegate>类型的对象在调用了它了Compile方法之后将得到一个TDelegate类型的委托对象,而调用一个委托对象与直接调用一个方法的性能开销相差无几。那么对于上面的情况,我们又该得到什么样的Delegate对象呢?为了使解决方案足够通用,我们必须将各种签名的方法统一至同样的委托类型中,如下:

public Func<object, object[], object> GetVoidDelegate()
{Expression<Action<object, object[]>> exp = (instance, parameters) => ((Program)instance).Call(parameters[0], parameters[1], parameters[2]);Action<object, object[]> action = exp.Compile();return (instance, parameters) =>{action(instance, parameters);return null;};
}

如上,我们就得到了一个Func<object, object[], object>类型的委托,这意味它接受一个object类型与object[]类型的参数,以及返回一个object类型的结果——等等,朋友们有没有发现,这个签名与MethodInfo类型的Invoke方法完全一致?不过可喜可贺的是,我们现在调用这个委托的性能远高于通过反射来调用了。那么对于有返回值的方法呢?那构造一个委托对象就更方便了:

public int Call(object o1, object o2) { return 0; }public Func<object, object[], object> GetDelegate()
{Expression<Func<object, object[], object>> exp = (instance, parameters) =>((Program)instance).Call(parameters[0], parameters[1]);return exp.Compile();
}

至此,我想朋友们也已经能够轻松得出调用静态方法的委托构造方式了。可见,这个解决方案的关键在于构造一个合适的Expression<TDelegate>,那么我们现在就来编写一个DynamicExecuter类来作为一个较为完整的解决方案:

public class DynamicMethodExecutor
{private Func<object, object[], object> m_execute;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<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo){// parameters to executeParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");// build parameter listList<Expression> parameterExpressions = new List<Expression>();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).MethodMethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);// ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)if (methodCall.Type == typeof(void)){Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceParameter, parametersParameter);Action<object, object[]> execute = lambda.Compile();return (instance, parameters) =>{execute(instance, parameters);return null;};}else{UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(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<TDelegate>类型的Compile方法正是使用Emit来生成委托对象。不过现在我们已经无需将目光放在更低端的IL上,只要使用高端的API来进行Expression Tree的构造,这无疑是一种进步。不过这种方法也有一定局限性,例如我们只能对公有方法进行调用,并且包含out/ref参数的方法,或者除了方法外的其他类型成员,我们就无法如上例般惬意地编写代码了。

补充

木野狐兄在评论中引用了Code Project的文章《A General Fast Method Invoker》,其中通过Emit构建了FastInvokeHandler委托对象(其签名与Func<object, object[], object>完全相同)的调用效率似乎较“方法直接”调用的性能更高(虽然从原文示例看来并非如此)。事实上FastInvokeHandler其内部实现与DynamicMethodExecutor完全相同,居然有如此令人不可思议的表现实在让人啧啧称奇。我猜测,FastInvokeHandler与DynamicMethodExecutor的性能优势可能体现在以下几个方面:

  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等等在这一领域都表现良好。

分类: 05. 实践优化, 01. DotNet框架
标签: 性能, Lambda表达式, 反射, Emit
绿色通道: 好文要顶 关注我 收藏该文与我联系 
8
0
(请您对文章做出评价)

« 博主前一篇:在Web应用程序开发过程中利用ASP.NET MVC框架的实战技巧
» 博主后一篇:和谐社区,和谐技术:微软的宠儿们,为什么富人的孩子就不能早当家?

  • Categories: 05. 实践优化, 01. DotNet框架
  • Tags: 性能, Lambda表达式, 反射, Emit

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言。

原文链接:http://www.cnblogs.com/jeffreyzhao/archive/2008/11/24/invoke-method-by-lambda-expression.html

转载于:https://www.cnblogs.com/ppcompany/articles/2713484.html

方法的直接调用,反射调用与……Lambda表达式调用相关推荐

  1. java lambda 反射_反射调用与Lambda表达式调用

    想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用--至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验 ...

  2. Java8 详解Lambda表达式

    1. 引言 java.util.function包 我们发现使用lambda表达式的时候,经常需要定义一些接口用来辅助我们的编码,这样就会使得本应轻量级的lambda表达式又变得重量级.那是否存在解决 ...

  3. Java 8 Lambda 表达式详解

    版权声明:本文由吴仙杰创作整理,转载请注明出处:https://segmentfault.com/a/1190000009186509 1. 引言 在 Java 8 以前,若我们想要把某些功能传递给某 ...

  4. 委托、Lambda表达式和事件

    1. 引用方法 委托是寻址方法的.NET版本.在C++中,函数指针只不过是一个指向内存位置的指针,它不是类型安全的.我们无法判断这个指针实际指向什么,像参数和返回类型等项就更无从知晓了.而.NET委托 ...

  5. 《Java8实战》第3章 Lambda 表达式

    利用行为参数化来传递代码有助于应对不断变化的需求.它允许你定义一段代码块来表示一个行为,然后传递它. 采用匿名类来表示多种行为并不令人满意:代码十分啰唆,这会影响程序员在实践中使用行为参数化的积极性. ...

  6. Java笔记 - 黑马程序员_08(Lambda表达式,接口组成更新,方法引用,类加载器,反射)

    1. Lambda 1.1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是"数据做操作" 面向对象思想强调"必须通过对象的形式来做事情&qu ...

  7. 【Kotlin】Lambda 表达式 ( 简介 | 表达式语法 | 表达式类型 | 表达式返回值 | 调用方式 | 完整示例 )

    文章目录 I . Lambda 表达式 简介 II . Lambda 表达式语法 III . Lambda 表达式类型 IV . Lambda 表达式返回值 V . Lambda 表达式调用 VI . ...

  8. 【Java】反射、枚举、Lambda表达式

    一.反射 1 定义 2 用途(了解) 3 反射基本信息 4 反射相关的类(重要) 4.1 Class类(反射机制的起源 ) 4.1.1 Class类中的相关方法(方法的使用方法在后边的示例当中) 4. ...

  9. 十三、Java高级特性 Lambda表达式 | 接口组成更新 | 方法引用 | 函数式接口

    文章目录 十三.Java高级特性 1.Lambda表达式 1.1体验Lambda表达式[理解] 1.2Lambda表达式的标准格式[理解] 1.3Lambda表达式练习1[应用] 1.4Lambda表 ...

最新文章

  1. QuickPart : 用户控件包装器 for SharePoint 2007
  2. tensorflow随笔-读写数据tf.data
  3. PHP ICO/STO Token销售管理面板/ICO管理程序开心版
  4. php写两个数相加方法_【PHP学习】实现两个n位二进制整数相加
  5. 深入比特币原理(五)——高级交易与脚本
  6. Python爬虫时中文乱码的处理
  7. 华为最强自研 NPU 问世,麒麟 810 测评远超骁龙系列!
  8. [smali] This Handler class should be static or leaks might occur
  9. goods购物表MySQL的代码_python采用sqlachmy购物商城
  10. resnet的演化(res2net,resnext,se-resnet,sk-resnet,resnest)
  11. DOTween 数字变化
  12. Verilog——计数器
  13. 15b万用表怎么测电容_FLUKE-15B+型数字万用表操作规程
  14. 学习笔记之Python的六种内置对象
  15. j1900做网站服务器,j1900可以跟云服务器
  16. 定制材料 GPTMS修饰的纳米二氧化硅(GPTMS-SiO2)/C18键合纳米金修饰二氧化硅颗粒/二氧化硅修饰咪唑型离子液体
  17. Python selenium 爬取淘宝商品
  18. 【Golang之路】——slice总结
  19. Cloud foundry基础
  20. 文件上传-01基础及过滤方式

热门文章

  1. java se是不是java_Java SE和java EE究竟有什么实质上的区别
  2. android手机定位p适配,Android 9(P)版本适配指南
  3. java语言概述、java语言特性、java语言发展史、java语言作用
  4. 女生学计算机未来出路,计算机真的已经烂大街了吗,女生学计算机没出路吗?...
  5. 项目不能上线,是开发的锅,还是产品的错?
  6. 互联网日报 | 社区团购“九不得”新规出台;小米11官宣12月28日发布;长征八号首飞成功...
  7. pandas读取excel带汉字的列头_Pandas在读取csv时如何设置列名--常用方法集锦
  8. 专题导读:大数据支撑的智能应用
  9. 基于生成对抗网络的医学数据域适应研究
  10. 【项目管理】项目进度管理的关键路径