求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
替代反射调用的几种方式及性能测试
 

发布于2013-7-30

 

园子里和这个话题的相关文章比较多,本文是旧话重提,外加个小的总结。主要因为近期看到很多同事、朋友都已经使用 VS2012 进行 .NET 4.5 开发了,却还在大量使用反射,不知道用新的方式。或有所了解,但又害怕性能不好不敢大胆去用。

本文以如下类为例:

public class MyMath
{
public int Add(int a, int b)
{
return a + b;
}
}

替代反射的几种方式

倒序说吧,从最先进最简单的开始。

1. dynamic 调用

.NET 4 引入了 dynamic 类型,可以使用如下方式来完成对 MyMath.Add 方法的动态调用:

dynamic math = new MyMath();
int result = math.Add(1, 2);

非常简单,效率也不错,可以看后面的性能对比测试结果。

但有一点要注意, dynamic 遵守 .NET 的访问级别限定,会对成员进行可见性检查。也就是说,只能 dynamic 调用 public 成员;当然,如果是同一程序集内部,internal 成员也是可以访问的。

2. Expression Tree 编译调用

Expression Tree 是 .NET 3.5 引入的。简单地,我们可以使用 lambda 构建一颗 Expression Tree:

var math = new MyMath();
Expression<Func<int, int, int>> add = (a, b) => math.Add(a, b);

这种方法适合手工编码构建,还有另外一种方式可以动态构建:

var add = typeof(MyMath).GetMethod("Add");
var math = Expression.Parameter(typeof(MyMath));
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var body = Expression.Call(myMath, add, a, b);
var lambda = Expression.Lambda<Func<MyMath, int, int, int>>(body, math, a, b);

两种方式构建出的 Tree 是相同的。

话归正题,构建出表达式树后,调用其 Compile 方法便可编译成一个委托,如下代码第 3 行:

var math = new MyMath();
Expression<Func<int, int, int>> addExpTree = (a, b) => math.Add(a, b); // ExressionTree
Func<int, int, int> add = addExpTree.Compile(); // 编译成委托
var result = add(1, 2); // 相加,结果为3

与 dynamic 调用方法同,Expression Tree 编译出的委托方法也遵守 .NET 的访问级别限定,会对成员进行可见性检查,不能访问私有成员。

3. 反射发出调用

这里只介绍反射发出的一项技术 DynamicMethod,.NET 2.0 新增此类。

使用 DynamicMethod 类在运行时定义轻量全局方法,然后使用委托执行这些方法。

针对 MyMath.Add 方法,调用比前面两种方式复杂些:

var addMethod = typeof(MyMath).GetMethod("Add");
var dynamicMethod = new DynamicMethod("", typeof(int), new[] { typeof(MyMath), typeof(int), typeof(int) });
//
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Callvirt, addMethod);
il.Emit(OpCodes.Ret);
//
var add = (Func<MyMath, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<MyMath, int, int, int>));
//
var math = new MyMath();
var result = add(math, 1, 2);

从第 5 行起,使用几个 IL 汇编指令,简单一说:

第 5 行,OpCodes.Ldarg_0 是将索引为 0 的参数值推送到堆栈上,Ldarg_1、Ldarg_2 以此类推;

第 6 行,OpCodes.Callvirt 是调用对象的(后期绑定)方法,并且将返回值推送到计算堆栈上;

第 9 行,OpCodes.Ret 表达从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。

反射发出是在汇编级别的,很底层,也就意味着效率更高、威力更强大。反射发出能绕过跳过 JIT 可见性检查,访问 private 成员(对于 DynamicMethod 类,请查看:DynamicMethod 构造函数 (String, Type, Type[], Boolean))。

下面是几种方法的性能测试。

性能对比测试

这里对直接、反射发出、dynamic 、表达式树编译、反射五种调用方式进行性能对比测试。

测试结果

先给出测试的结果:

从上图中可以看出:

直接调用性能最佳;

反射发出和表达式树两种方式性能相当,速度接近直接调用;

dynamic 性能居中,也不错;

反射方式性能最差。

另外说明两点:

本次测试仅针对 MyMath.Add 方法,其参数和返回值都是值类型,反射调用时存在大量装箱、拆箱。如果测试方法的参数和返回值都是引用类型,反射方式与其它方式间的差距会小些。

从上图可以看出这几次方式性能差别较大,但此结果是重复 100 万次的情况下得出的。考虑单次调用,反射只比直接调用慢 381 纳秒。如果你的代码不是位于循环的中心或是系统的瓶颈,调用次数不多,性能差异可以完全忽略。

测试代码

以下是测试用代码,仅参考:

using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
public class MyMath {
public int Add(int a, int b) { return a + b; }
}
class Program {
static void Main(string[] args) {
int result;
var math = new MyMath();
var count = 1000000;
Console.WriteLine("数据量:" + count);
Console.WriteLine("-----------------------------r\n");
using (Profiler.Step("循环:{0} ms")) {
for (int i = 0; i < count; i++)
result = 1;
}
using (Profiler.Step("直接调用 :{0} ms")) {
for (int i = 0; i < count; i++)
result = math.Add(i, i);
}
using (Profiler.Step("反射发出:{0} ms")) {
var emitAdd = BuildEmitAddFunc();
for (int i = 0; i < count; i++)
result = emitAdd(math, i, i);
}
using (Profiler.Step("表达式树:{0} ms")) {
var expressionAdd = BuildExpressionAddFunc();
for (int i = 0; i < count; i++)
result = expressionAdd(math, i, i);
}
using (Profiler.Step("dynamic 调用:{0} ms")) {
dynamic d = math;
for (int i = 0; i < count; i++)
result = d.Add(i, i);
}
using (Profiler.Step("反射调用:{0} ms")) {
var add = typeof(MyMath).GetMethod("Add");
for (int i = 0; i < count; i++)
result = (int)add.Invoke(math, new object[] { i, i });
}
Console.WriteLine("\r\n\r\n测试完成,任意键退出...");
Console.ReadKey();
}
static Func<MyMath, int, int, int> BuildExpressionAddFunc() {
var add = typeof(MyMath).GetMethod("Add");
var math = Expression.Parameter(typeof(MyMath));
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var body = Expression.Call(math, add, a, b);
var lambda = Expression.Lambda<Func<MyMath, int, int, int>>(body, math, a, b);
return lambda.Compile();
}
static Func<MyMath, int, int, int> BuildEmitAddFunc() {
var add = typeof(MyMath).GetMethod("Add");
var dynamicMethod = new DynamicMethod("", typeof(int), new[] { typeof(MyMath), typeof(int), typeof(int) });
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Callvirt, add);
il.Emit(OpCodes.Ret);
return (Func<MyMath, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<MyMath, int, int, int>));
}
}

Profiler 是我写的一个类,用于简化测试:

using System;
using System.Diagnostics;
public class Profiler : IDisposable {
private Stopwatch watch;
private string message;
private Profiler(string message) {
this.watch = new Stopwatch();
this.watch.Start();
this.message = message;
}
public void Dispose() {
watch.Stop();
Console.WriteLine(message, watch.ElapsedMilliseconds);
Console.WriteLine();
}
public static IDisposable Step(string message) {
return new Profiler(message);
}
public static T Inline<T>(string message, Func<T> func) {
using (new Profiler(message))
return func();
}
}

总结

综上所述,我们可以使用 .NET 2.0 的 DynamicMethod,.NET 3.5 引入的 Expression Tree、.NET 4 新增的 dynamic 来替换反射调用,带来更好的性能。

希望本文对大家有所帮助,也希望整天忙于项目、疲于工作的朋友抽点时间学习下 .NET Framework 的一些“新”特性。

相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践
 
分享到
 
 
     


LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...