重复造轮子感悟 – XLinq性能提升心得
曾经的两座大山
1、EF
刚接触linq那段时间,感觉这家伙好神奇,语法好优美,好厉害。后来经历了EF一些不如意的地方,就想去弥补,既然想弥补,就必须去了解原理。最开始甚至很长一段时间都搞不懂IQueryProvider(小声说,其实现在也没完全搞懂),好不容易把IQueryProvider搞懂了,然后才发现好戏才刚刚开始,这个时候尝试写了第一个ORM。那个时候不明白表达式树的原理,自然一开始的思路就是走一点算一点,走到后面就没法走了,因为思路太乱了。这个时候就感觉EF太牛了,那么复杂的linq都能翻译出来,虽然翻译的sql的质量不行,而且还有坑,不过至少翻译出来了,感觉自己永远都没办法做到。
2、Dapper
这框架是大名鼎鼎的,性能是相当高的,底层是用EMIT写的。然而我这人就是有技术洁癖,如果是我自己一个人开发,那么如果这个工具让我感觉到了不爽,我就会尝试自己开发。所以我也没用dapper,原因只是它直接操作了Connection,而且要手写sql代码。但是我自己写的在性能上始终是个硬伤,跟dapper比不了。之前我业余用表达式树实现了DataSet到List的转换,然后拿到公司装逼,同事来了一句"来来咱跟dapper比比",我说"得得你就别虐我了成不",比的结果当然是比较惨的,那个时候我觉得我不可能达到dapper的转换速度。
性能提升测试
测试代码
- static void Main(string[] args)
- {
- EFDbContext db = new EFDbContext();
- db.Configuration.AutoDetectChangesEnabled = false;
- db.Configuration.LazyLoadingEnabled = false;
- db.Configuration.ValidateOnSaveEnabled = false;
- XLinqDataContext xlinq = new XLinqDataContext();
- db.Users.Where(x => false).ToList();//让EF完成初始化
- ExecuteTimer("EF20万数据查询", () =>
- {
- db.LargUsers.Take(200000).ToList();
- });
- GC.Collect();
- ExecuteTimer("XLinq20万数据查询", () =>
- {
- var a = xlinq.Set<LargeUser>().Take(200000).ToList();
- });
- GC.Collect();
- ExecuteTimer("Dapper20万数据查询", () =>
- {
- SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString);
- var a = conn.Query<LargeUser>("SELECT top 200000 * from dbo.largeusers");
- });
- GC.Collect();
- ExecuteTimer("XLinq50万数据查询", () =>
- {
- var a = xlinq.Set<LargeUser>().Take(500000).ToList();
- });
- GC.Collect();
- ExecuteTimer("Dapper50万数据查询", () =>
- {
- SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString);
- var a = conn.Query<LargeUser>("SELECT top 500000 * from dbo.largeUsers");
- });
- Console.ReadKey();
- }
- static void ExecuteTimer(string name, Action action)
- {
- var watch = Stopwatch.StartNew();
- action();
- watch.Stop();
- Console.WriteLine(string.Format("{0}:{1}毫秒", name, watch.ElapsedMilliseconds));
- }
测试结果
秒EF是妥妥的,但dapper基本上性能一样,那点差距就直接忽略吧。
之前也进行过一次,被dapper秒,和EF性能一样,原因是因为字典缓存用的有问题。
而这次这个最终取数据全部用的数组,一开始其实把dapper都给秒了,快了dapper接近一半,不过后来发现情况没考虑全,考虑全了就差不多了。
感悟和心得
- Expression Tree与EMIT
应该有不少人都认为后者比前者快,我一开始也这么认为的。
但园子一位大神说,Expression Tree与EMIT最终生成的代码是一样的,所以性能是一样的。说实在的,我还是不太信。
然后老赵大神又说,Expression Tree最终也是操作的EMIT来实现的。好吧,其实我有点信了,但还是不太信。
直到我在写XLinq的时候用纯Expression Tree超越了用纯EMIT的Dapper,我才相信。(不信?可以在评论里要源码)
- 基础类型转换的坑
之前造轮子的时候,一直觉得Convert.ChangeType是尚方宝剑,一剑在手,天下我有。
然而在写XLinq的时候,被这个方法坑的不轻。
当我要long、int、short、int?、long?、short?这几个类型相互转换的时候,总会冒出来异常。
然后谷歌一下,园子大神的博文说:"这个方法一遇到Nullable<T>类型的时候就会挂掉"。
好吧,我认了(说错了的话请指正)。然后不得不自己处理类型转换,同时也算是知道了这个坑。
- LINQ并不能完美实现无缝切换数据库
一直认为只要linq provider写得足够好,就能支持无缝切换数据库,注意是切换不是支持。
然而现在看来,想要做到直接改一下配置文件就能切换这是不可能的。
举一个例子,假如说要将sql server数据库切换到sqlite,这个时候切换过去之后看起来其实不会有什么问题,因为sqlite即使语法不对也可能不会报错。
在sql server中,2015/1/1这是合法的日期型数据,但这样的数据在sqlite中是无法识别的。
也就是说,如果你之前用的sql server,并且使用了这样的数据格式,那么切换到sqlite后可能所有关于日期的判断会全部出错,并不是指报异常,而是说计算结果不正确。
- 将IDataReader转换到List的功能整个直接生成代码,不再生成每一个属性的委托
之前在写类似于ORM的工具时,总会将先生成每一个属性的Setter和Getter委托,然后再单独调用这些委托来完成转换。
这样就几乎无法避免装箱拆箱的问题,但装箱拆箱其实还是会浪费一点性能的。
后来想到了另一种办法,不再生成Setter和Getter的委托,转而生成包装了一个循环的委托。
就是说,我传给委托一个类型和一个Reader对象,它就直接返回给我一个List,我不用去获取每一个属性的委托。
委托中直接生成了访问该类型的每一个属性的代码,这样就直接避免了装箱拆箱,最终性能跟dapper差不多了。
不过这样做可能会占用多一点缓存,不过现在来说内存应该不是问题
IDataReader转List关键代码
public static Func<IDataReader, IList> GetDataReaderMapeer(Type type,IDataReader reader)
{
Func<IDataReader, IList> func = null;
if (!_dataReader2ListCahce.TryGetValue(type, out func))
{
lock (_dataReader2ListCahce)
{
if (!_dataReader2ListCahce.TryGetValue(type, out func))
{
var readerExp = Expression.Parameter(ReflectorConsts.IDataReaderType, "reader");
var properties = ExpressionReflector.GetProperties(type);
var fieldCount = reader.FieldCount;
var expressions = new List<Expression>();
var objVar = Expression.Variable(type, "entity");
var fieldCountVar = Expression.Variable(ReflectorConsts.Int32Type, "fieldCount");
var readerVar = Expression.Variable(ReflectorConsts.IDataReaderType, "readerVar");
var propertyNameArr = Expression.Variable(ReflectorConsts.StringArrayType, "pis");
var indexArrVar = Expression.Variable(ReflectorConsts.Int32ArrayType, "indexes");
var readIndexVar = Expression.Variable(ReflectorConsts.Int32Type, "readIndex");
var indexVar = Expression.Variable(ReflectorConsts.Int32Type, "index");
var forBreakLabel = Expression.Label("forBreak");
var assignIndexVar = Expression.Assign(indexVar, Expression.Constant());
var listType = ReflectorConsts.ListType.MakeGenericType(type);
var listVar = Expression.Variable(listType, "list");
expressions.Add(Expression.Assign(listVar, Expression.New(listType)));
expressions.Add(Expression.Assign(readerVar, readerExp));
expressions.Add(assignIndexVar);
var assignFieldCountVar = Expression.Assign(fieldCountVar,
Expression.MakeMemberAccess(readerVar, ReflectorConsts.FieldCountOfIDataReader)
);
expressions.Add(assignFieldCountVar);
var readNameExp = Expression.Call(readerVar, ReflectorConsts.GetOrdinalOfIDataReader, Expression.ArrayIndex(propertyNameArr, indexVar));
var initIndexArray = Expression.Assign(indexArrVar, Expression.NewArrayBounds(ReflectorConsts.Int32Type, Expression.Constant(fieldCount)));
var initPropertyArrayExpressions = new List<Expression>();
for (int i = ; i < fieldCount; i++)
{
initPropertyArrayExpressions.Add(Expression.Constant(reader.GetName(i)));
}
var initPropertyArray = Expression.Assign(propertyNameArr, Expression.NewArrayInit(ReflectorConsts.StringType, initPropertyArrayExpressions));
var assignIndexArrayVar = Expression.Assign(Expression.ArrayAccess(indexArrVar, indexVar), readNameExp);
expressions.Add(initPropertyArray);
expressions.Add(initIndexArray);
expressions.Add(Expression.Loop(
Expression.IfThenElse(
Expression.LessThan(indexVar, fieldCountVar),
Expression.Block(
assignIndexArrayVar,
Expression.Assign(
indexVar,
Expression.Add(indexVar, Expression.Constant())
)
),
Expression.Break(forBreakLabel)
),
forBreakLabel
));
Expression body = null;
DataReaderGetMethodSwitcher switcher = null;
var labelTarget = Expression.Label(type, "return");
var paramterExpressions = new List<ParameterExpression>();
var setEntityExpressions = new List<Expression>();
if (TypeHelper.IsCompilerGenerated(type))
{
var constructor = type.GetConstructors().FirstOrDefault();
if (constructor == null)
{
throw new ArgumentException("类型" + type.FullName + "未找到构造方法");
}
var parameters = constructor.GetParameters();
var expressionParams = new List<ParameterExpression>();
for (int i = ; i < fieldCount; i++)
{
var parameter = parameters[i];
var parameterVar = Expression.Variable(parameter.ParameterType, parameter.Name);
var parameterType = TypeHelper.GetUnderlyingType(parameter.ParameterType);
switcher = new DataReaderGetMethodSwitcher(parameterType, readIndexVar, readerVar);
switcher.Process();
var rightExp = (Expression)switcher.Result;
if (TypeHelper.IsNullableType(parameter.ParameterType))
{
rightExp = Expression.Convert(rightExp, parameter.ParameterType);
}
var isNullExp = Expression.Call(readerExp, ReflectorConsts.IsDBNullfIDataReader, readIndexVar);
var ifExp = Expression.IfThenElse(isNullExp, Expression.Assign(parameterVar, Expression.Default(parameter.ParameterType)), Expression.Assign(parameterVar, rightExp));
var exps = new List<Expression>();
setEntityExpressions.Add(
Expression.Assign(
readIndexVar,
Expression.ArrayIndex(indexArrVar, Expression.Constant(i))
)
);
setEntityExpressions.Add(ifExp);
expressionParams.Add(parameterVar);
}
setEntityExpressions.Add(Expression.Assign(objVar, Expression.New(constructor, expressionParams)));
paramterExpressions.AddRange(expressionParams);
paramterExpressions.Add(readerVar);
paramterExpressions.Add(listVar);
}
else
{
var newExp = Expression.New(type);
setEntityExpressions.Add(Expression.Assign(objVar, newExp));
for (int i = ; i < fieldCount; i++)
{
var propertyName = reader.GetName(i);
var property = properties.Get(propertyName);
if (property == null)
{
continue;
}
var propertyAssignExpressions = new List<Expression>();
var propertyExp = Expression.Property(objVar, property);
var propertyType = TypeHelper.GetUnderlyingType(property.PropertyType);
Expression rightExp = null;
switcher = new DataReaderGetMethodSwitcher(propertyType, readIndexVar, readerVar);
switcher.Process();
rightExp = (Expression)switcher.Result;
if (TypeHelper.IsNullableType(property.PropertyType))
{
rightExp = Expression.Convert(rightExp, property.PropertyType);
}
setEntityExpressions.Add(
Expression.Assign(
readIndexVar,
Expression.ArrayIndex(indexArrVar, Expression.Constant(i))
)
);
var ifExp = Expression.IfThen(
Expression.Not(
Expression.Call(readerExp, ReflectorConsts.IsDBNullfIDataReader, readIndexVar)
),
Expression.Assign(propertyExp, rightExp)
);
setEntityExpressions.Add(ifExp);
}
paramterExpressions.Add(listVar);
paramterExpressions.Add(readerVar);
}
paramterExpressions.Add(indexVar);
paramterExpressions.Add(propertyNameArr);
paramterExpressions.Add(fieldCountVar);
paramterExpressions.Add(indexArrVar);
paramterExpressions.Add(readIndexVar);
//expressions.Add(Expression.Call(
// null,
// typeof(MessageBox).GetMethod("Show", new Type[] { ReflectorConsts.StringType }),
// Expression.Call(
// null,
// ReflectorConsts.ConvertToStringMethod,
// Expression.Convert(
// Expression.ArrayIndex(indexArrVar, Expression.Constant(1)),
// ReflectorConsts.ObjectType)
// )));
setEntityExpressions.Add(Expression.Call(listVar, listType.GetMethods().FirstOrDefault(x => x.Name == "Add"), objVar));
expressions.Add(
Expression.Loop(
Expression.Block(
Expression.IfThenElse(
Expression.Call(readerVar, ReflectorConsts.ReadOfIDataReader),
Expression.Block(new[] { objVar }, setEntityExpressions),
Expression.Break(labelTarget, Expression.Default(type))
)
),
labelTarget
));
expressions.Add(listVar);
body = Expression.Block(
paramterExpressions,
expressions
);
func = Expression.Lambda<Func<IDataReader, IList>>(body, readerExp).Compile();
_dataReader2ListCahce.Add(type, func);
}
}
}
return func;
}
重复造轮子感悟 – XLinq性能提升心得的更多相关文章
- GitHub Android 最火开源项目Top20 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上。基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。
1. ActionBarSherlock(推荐) ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便 ...
- 重复造轮子,编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)
一说到写日志,大家可能推荐一堆的开源日志框架,如:Log4Net.NLog,这些日志框架确实也不错,比较强大也比较灵活,但也正因为又强大又灵活,导致我们使用他们时需要引用一些DLL,同时还要学习各种用 ...
- Meteva——让预报检验不再重复造轮子
更多精彩,请点击上方蓝字关注我们! 检验是什么?****预报准确率的客观表达 说到天气预报,你最先会想到什么? 早上听了预报,带了一天伞却没下一滴雨的调侃? 还是 "蓝天白云晴空万里突然暴风 ...
- 答应我,用了这个jupyter插件,别再重复造轮子了
1 简介 在使用Python.R等完成日常任务的过程中,可能会经常书写同样或模式相近的同一段代码,譬如每次使用matplotlib绘制图像的时候可以在开头添加下面两行代码来解决中文乱码等显示问题: p ...
- 54 个官方 Spring Boot Starters 出炉!别再重复造轮子了…….
在之前的文章,栈长介绍了 Spring Boot Starters,不清楚的可以点击链接进去看下. 前段时间 Spring Boot 2.4.0 也发布了,本文栈长再详细总结下最新的 Spring B ...
- 避免重复造轮子的UI自动化测试框架开发
一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...
- 第27篇 重复造轮子---模拟IIS服务器
在写程序的时候,重复造轮子是程序员的一个大忌,很多人对重复造轮子持有反对的态度,但是我觉得这个造轮子的过程,是对于现有的知识的一个深入的探索的过程,虽然我们不可能把轮子造的那么的完善,对于现在有的东西 ...
- Light libraries是一组通用的C基础库,目标是为减少重复造轮子而写(全部用POSIX C实现)
Light libraries是一组通用的C基础库,目标是为减少重复造轮子而写实现了日志.原子操作.哈希字典.红黑树.动态库加载.线程.锁操作.配置文件.os适配层.事件驱动.工作队列.RPC.IPC ...
- 重复造轮子系列——基于Ocelot实现类似支付宝接口模式的网关
重复造轮子系列——基于Ocelot实现类似支付宝接口模式的网关 引言 重复造轮子系列是自己平时的一些总结.有的轮子依赖社区提供的轮子为基础,这里把使用过程的一些觉得有意思的做个分享.有些思路或者方法在 ...
随机推荐
- CodeForces 546C(队列)
CodeForces 546C Soldier and Cards Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I ...
- Java中关键字super与this的区别
一.super关键字 在JAVA类中使用super来引用父类的成分,用this来引用当前对象,如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象.怎么去引 ...
- 构建高可用web站点(五)
数据库是web站点中重要的应用,放在第四篇是因为之前来不及总结的原因,在之前的文章我看到了无论是Mysql或者是nosql的一些缓存和分布式一些比较扩展性的功能.但是对于单个数据库来说,它的优化也是我 ...
- kd树的构建以及搜索
构建算法 k-d树是一个二叉树,每个节点表示一个空间范围.表1给出的是k-d树每个节点中主要包含的数据结构. 表1 k-d树中每个节点的数据类型 域名 数据类型 描述 Node-data 数据矢量 数 ...
- <<开源硬件创客 15个酷应用玩转树莓派>>
本书共分18章,前3章是本书的基础章节,主要介绍了树莓派的一些基本情况和基本操作,来让读者了解树莓派的前世今生,掌握树莓派基本的使用方法.第4~18章主要介绍15个以树莓派为载体的酷炫应用,大家可以按 ...
- Linux&shell之结构化命令
写在前面:案例.常用.归类.解释说明.(By Jim)使用if-then语句如果命令的退出状态是0(成功执行命令),将执行then后面的所有命令.如果命令的退出状态是0以外的其他值,那么then后面的 ...
- 跑马灯js
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- cf590A Median Smoothing
A. Median Smoothing time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- Mustache.js语法学习笔记
原文地址:http://www.cnblogs.com/flypig88/archive/2012/05/14/2497780.html 看了Mustache的github,学学其中的语法,做个笔记 ...
- IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事
IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事 IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)