ExpressionTree——让反射性能向硬编码看齐
缘起
最近又换了工作。然后开心是以后又能比较频繁的关注博客园了。办离职手续的这一个月梳理了下近一年自己写的东西,然后就有了此文以及附带的代码。
反射
关于反射,窃以为,他只是比较慢。在这个前提下,个人认为只有在进行集合操作的时候谈论反射的性能才有意义。同时,只有在这个集合达到一定规模的时候,才有改进的余地。比如说,1000个元素的集合。后文会给出详细的数据。
关于ExpressionTree的介绍
借花献佛:
http://www.cnblogs.com/ASPNET2008/archive/2009/05/22/1485888.html
http://www.cnblogs.com/jams742003/archive/2009/12/23/1630432.html
实现
先给例子,再来探讨为什么。这里是几个Case:
1.根据属性名提取一个IEnuerable<object>集合元素的属性/字段的值。
2.根据属性名更新上诉对象的元素的属性/字段值。
3.将上诉集合投影为指定元素类型的集合。
以下是针对这3个Case的实现:
1.加载属性
using System;
using System.Collections.Generic;
using System.Linq;
using System.Caching;
using System.Text;
using System.Extension;
using System.Collections.Concurrent; namespace System.Linq.Expressions
{
public class PropertyFieldLoader : CacheBlock<string, Func<object, object>>
{
protected PropertyFieldLoader() { } public object Load(object tObj, Type type, string propertyPath)
{
return Compile(type, propertyPath)(tObj);
} public T Load<T>(object tObj, Type type, string propertyPath)
where T : class
{
return Load(tObj, type, propertyPath) as T;
} public Func<object, object> Compile(Type type, string propertyPath)
{
var key = "Get_" + type.FullName + "_";
var func = this.ConcurrentDic.GetOrAdd(key + "." + propertyPath, (string k) =>
{
ParameterExpression paramInstance = Expression.Parameter(typeof(object), "obj");
Expression expression = Expression.Convert(paramInstance, type);
foreach (var pro in propertyPath.Split('.'))
expression = Expression.PropertyOrField(expression, pro);
expression = Expression.Convert(expression, typeof(object));
var exp = Expression.Lambda<Func<object, object>>(expression, paramInstance);
return exp.Compile();
});
return func;
} public static PropertyFieldLoader Instance = new PropertyFieldLoader();
}
}
2.更新属性
using System;
using System.Collections.Generic;
using System.Linq;
using System.Caching;
using System.Text;
using System.Extension;
using System.Collections.Concurrent; namespace System.Linq.Expressions
{
public class PropertyFieldSetter : CacheBlock<string, Action<object, object>>
{
protected PropertyFieldSetter() { } public void Set(object obj, string propertyPath, object value)
{
var tObj = obj.GetType();
var tValue = value.GetType();
var act = Compile(tObj, tValue, propertyPath);
act(obj, value);
} public static PropertyFieldSetter Instance = new PropertyFieldSetter(); public Action<object, object> Compile(Type typeObj,Type typeValue, string propertyPath)
{
var key = "Set_" + typeObj.FullName + "_" + typeValue.FullName;
var act = ConcurrentDic.GetOrAdd(key + "." + propertyPath, (s) =>
{
ParameterExpression paramInstance = Expression.Parameter(typeof(object), "obj");
ParameterExpression paramValue = Expression.Parameter(typeof(object), "value");
Expression expression = Expression.Convert(paramInstance, typeObj);
foreach (var pro in propertyPath.Split('.'))
expression = Expression.PropertyOrField(expression, pro);
var value = Expression.Convert(paramValue, typeValue);
expression = Expression.Assign(expression, value);
var exp = Expression.Lambda<Action<object, object>>(expression, paramInstance, paramValue);
return exp.Compile();
});
return act;
}
}
}
3.数据转换
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Caching; namespace System.Linq.Expressions
{
public class DataTransfer<T> : CacheBlock<string, Func<object, T>>
where T : new()
{
protected DataTransfer() { } public Func<object, T> Compile(Type inType)
{
var outType = typeof(T);
var pKey = "Transfer_" + inType.FullName + "_" + outType.FullName; var func = this.ConcurrentDic.GetOrAdd(pKey, (string ckey) =>
{
var proOrFie = outType.GetProperties().Cast<MemberInfo>()
.Union(outType.GetFields().Cast<MemberInfo>())
.Union(inType.GetProperties().Cast<MemberInfo>())
.Union(inType.GetFields().Cast<MemberInfo>())
.GroupBy(i => i.Name).Where(i => i.Count() == )
.ToDictionary(i => i.Key, i => i); var returnTarget = Expression.Label(outType);
var returnLabel = Expression.Label(returnTarget, Expression.Default(outType));
var pramSource = Expression.Parameter(typeof(object));
var parmConverted = Expression.Convert(pramSource, inType);
var newExp = Expression.New(outType);
var variate = Expression.Parameter(outType, "instance");
var assVar = Expression.Assign(variate, newExp); Expression[] expressions = new Expression[proOrFie.Count + ];
expressions[] = assVar;
var assExps = proOrFie.Keys.Select(i =>
{
var value = Expression.PropertyOrField(parmConverted, i);
var prop = Expression.PropertyOrField(variate, i);
var assIgnExp = Expression.Assign(prop, value);
return assIgnExp;
});
var index = ;
foreach (var exp in assExps)
{
expressions[index] = exp;
index++;
} expressions[index] = Expression.Return(returnTarget,variate);
expressions[index + ] = returnLabel;
var block = Expression.Block(new[] { variate }, expressions); var expression = Expression.Lambda<Func<object, T>>(block, pramSource);
return expression.Compile();
}); return func;
} public static DataTransfer<T> Instance = new DataTransfer<T>();
}
}
性能测试
首先,使用ExpressionTree在第一次调用的时候,会产生非常明显的性能开销。所以,在进行测试之前,都会对各个方法预调用一次,从而缓存ET(简写)的编译(不带引号)结果。同事,为了公平起见,对比的基于反射实现的代码页进行了略微的处理。
对比代码:
1.加载属性
() => ducks.Select(i => pro.GetValue(i)).ToArray()
2.更新属性
foreach (var duck in ducks) { pro.SetValue(duck, "AssignedReflection"); }
3.数据转换
public static Dictionary<string, Tuple<PropertyInfo, PropertyInfo>> Analyze(Type ts, Type to)
{
var names = to.GetProperties()
.Union(ts.GetProperties())
.GroupBy(i => i.Name).Where(i => i.Count() == )
.Select(i => i.Key);
var result = ts.GetProperties()
.Where(i => names.Contains(i.Name)).OrderBy(i => i.Name)
.Zip(to.GetProperties().Where(i => names.Contains(i.Name)).OrderBy(i => i.Name), (a, b) => new { Key = a.Name, Item1 = a, Item2 = b })
.ToDictionary(i => i.Key, i => new Tuple<PropertyInfo, PropertyInfo>(i.Item1, i.Item2));
return result;
} /// <summary>
/// Item1:Source,Item2:Target
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="OutT"></typeparam>
/// <param name="source"></param>
/// <param name="refResult"></param>
/// <returns></returns>
public static IEnumerable<OutT> TransferByReflection<T, OutT>(IEnumerable<T> source,
Dictionary<string, Tuple<PropertyInfo, PropertyInfo>> refResult) where OutT : new()
{
foreach (var sor in source)
{
var target = new OutT();
foreach (var p in refResult)
{
object value = p.Value.Item1.GetValue(sor, null);
p.Value.Item2.SetValue(target, value);
}
yield return target;
}
}
同时还和对应的硬编码进行了对比,这里就不贴代码了。以下是结果(Tick为时间单位(1/10000毫秒)):
| ExpressionTree | NativeCode | Reflection | ArrayLenth | Benefits | Action |
| 26 | 12 | 21 | 10 | -5 | AccessProperty |
| 49 | 24 | 90 | 100 | 41 | AccessProperty |
| 224 | 140 | 842 | 1000 | 618 | AccessProperty |
| 2201 | 1294 | 7807 | 10000 | 5606 | AccessProperty |
| 35884 | 14730 | 77336 | 100000 | 41452 | AccessProperty |
| 223865 | 138630 | 789335 | 1000000 | 565470 | AccessProperty |
| ExpressionTree | NativeCode | Reflection | ArrayLenth | Benefits | Action |
| 14 | 5 | 17 | 10 | 3 | AssignValue |
| 23 | 10 | 101 | 100 | 78 | AssignValue |
| 109 | 45 | 983 | 1000 | 874 | AssignValue |
| 931 | 410 | 10542 | 10000 | 9611 | AssignValue |
| 9437 | 3720 | 116147 | 100000 | 106710 | AssignValue |
| 94895 | 45392 | 1064471 | 1000000 | 969576 | AssignValue |
| ExpressionTree | NativeCode | Reflection | ArrayLenth | Benefits | Action |
| 47 | 11 | 11 | 10 | -36 | TransferData |
| 101 | 46 | 744 | 100 | 643 | TransferData |
| 538 | 240 | 7004 | 1000 | 6466 | TransferData |
| 4945 | 2331 | 77758 | 10000 | 72813 | TransferData |
| 91831 | 23606 | 785472 | 100000 | 693641 | TransferData |
| 960657 | 681635 | 8022245 | 1000000 | 7061588 | TransferData |
同时,这里附上对应的图表:

由以上图表可知,当元素小于1000个的时候,收益不会超过1毫秒。所以,此时的改进空间可以说是几乎没有。当然,如果在整个项目中有很多地方,比如说...1000个地方,使用了类似的代码(反射),确实会导致性能问题。但这已经不是[反射]的错了。
原理和理解
上诉代码做的事情其实只有一件:“将逻辑缓存起来。”为什么说是将逻辑缓存起来?这是因为个人觉得,ET其实是描述代码逻辑的一种手段,和编程语言差不到哪里去了。只不过它编译的时候没有语法解析这一步。所以和调用编译器动态编译相比,“性能更好”也更轻量(臆测)。
而“为什么ET比反射快”,这个问题实际上应该是“为什么反射比ET慢”。反射调用的时候,首先要搜索属性,然后进行操作的时候又要进行大量的类型转换和校验。而对于ET而言,这些操作会在第一次完成之后“固定”并缓存起来。
收获
通过此次探讨,我们收获了什么呢?
1.只要小心一点,反射根本不是噩梦,让那些反射黑闭嘴。
2.就算反射导致了问题,我们仍然有办法解决。从以上数据来看,耗时为硬编码的两倍左右。如果2X->∞,那X也好不到哪里去。
3.好用。通过这种方式,我们能非常方便的访问“属性的属性”。比如:
ducks.SelectPropertyOrField<Duck, object>("Name.Length")
4.更多可能性。我们甚至可以在不产生太大的性能开销的情况下完成一个深拷贝组件,来实现一定程度上通用的深拷贝。
附件
这里附上我使用的代码,供大家参考。
http://pan.baidu.com/s/1c0dF3FE(OneDrive不给力)
ExpressionTree——让反射性能向硬编码看齐的更多相关文章
- 利用表达式树Expression优化反射性能
最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下. Excel导入是相对比较麻烦的一块, ...
- 如何利用缓存机制实现JAVA类反射性能提升30倍
一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...
- 直播二:iOS中硬编码(VideoToolBox)
硬编码相对于软编码来说,使用非CPU进行编码,如显卡GPU.专用的DSP.FPGA.ASIC芯片等,性能高,对CPU没有压力,但是对其他硬件要求较高(如GPU等). 在iOS8之后,苹果开放了接口,并 ...
- C# 之 反射性能优化1
反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性. ...
- Nvidia NVENC 硬编码预研总结
本篇博客记录NVENC硬编码的预研过程 github: https://github.com/MarkRepo/NvencEncoder 步骤如下: (1)环境搭建 (2)demo编译,测试,ARG ...
- windows 平台 ffmeg h264 硬编码
本文讲述windows 平台下ffmpeg如何利用intel media SDK 进行 h264硬编码(测试版本为3.2.2). ffmeg硬编编码的流程与软件编码流程相同,唯一不同的地方在初始化en ...
- 使用java8的方法引用替换硬编码
背景 想必大家在项目中都有遇到把一个列表的多个字段累加求和的情况,也就是一个列表的总计.有的童鞋问,这个不是给前端做的吗?后端不是只需要把列表返回就行了嘛...没错,我也是这样想的,但是在一场和前端的 ...
- iOS视频硬编码技术
iOS视频硬编码技术 一.iOS视频采集硬编码 基本原理 硬编码 & 软编码 硬编码:通过系统自带的Camera录制视频,实际上调用的是底层的高清编码硬件模块,即显卡,不使用CPU,速度快 软 ...
- Android安全开发之浅谈密钥硬编码
Android安全开发之浅谈密钥硬编码 作者:伊樵.呆狐@阿里聚安全 1 简介 在阿里聚安全的漏洞扫描器中和人工APP安全审计中,经常发现有开发者将密钥硬编码在Java代码.文件中,这样做会引起很大风 ...
随机推荐
- Javascript实现图片无缝滚动
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- SQL Server(基本) 关键字的使用 一
一, 基础关键字 -- 使用介绍 1,select 的使用(select 结果集) SELECT 列名称 FROM 表名称 以及: (*)是选取所有列的快捷方式. SELECT * FROM 表名称 ...
- 使用SqlBulkCopy类批量复制大数据
using System; using System.Configuration; using System.Data; using System.Data.SqlClient; using Syst ...
- Oracle Text Slowly
When oracle text more and more slowly, execute the following script: ANALYZE TABLE Table_Name COMPU ...
- Strut2文件下载
Struts2控制文件下载,可以在文件下载之前做一些操作.这里就以权限控制为例,简单实现一下Struts2的文件下载. 一.Struts2文件下载的Action配置,是提供了一个能返回InputStr ...
- (转)linux下mysql的安装过程
最近在linux安装了mysql,根据网上收集的资料和个人的操作过程,大概做了个整理,以便以后进行参考回顾. 1.下载mysql-5.1.36.tar.gz,并且解压. tar -xzvf mysql ...
- 字符设备驱动、平台设备驱动、设备驱动模型、sysfs的比较和关联
转载自:http://www.kancloud.cn/yueqian_scut/emlinux/106829 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sy ...
- Linux---vi编辑器必会操作
移动光标: (1)基本的上下左右:通过箭头按键控制 (2)跳到一行的末尾:键盘"end" (3)跳到一行的开头:键盘"home" (4)跳到最后一行:shift ...
- IE兼容性问题
1.H5标签兼容.解决:js:document.createElement("footer");css:display: block;或者直接使用 html5shiv.js ...
- Google设计理念
Google的十大信条 我们首次拟就这“十大信条”还是在Google刚刚成立没几年的时候.此后,我们时常重新审视这份清单,看看它是否依然适用.我们希望这些信条永不过时,而您也可以监督我们是否遵守了这些 ...