zoukankan      html  css  js  c++  java
  • 重复造轮子感悟 – XLinq性能提升心得

    曾经的两座大山

    1、EF

    刚接触linq那段时间,感觉这家伙好神奇,语法好优美,好厉害。后来经历了EF一些不如意的地方,就想去弥补,既然想弥补,就必须去了解原理。最开始甚至很长一段时间都搞不懂IQueryProvider(小声说,其实现在也没完全搞懂),好不容易把IQueryProvider搞懂了,然后才发现好戏才刚刚开始,这个时候尝试写了第一个ORM。那个时候不明白表达式树的原理,自然一开始的思路就是走一点算一点,走到后面就没法走了,因为思路太乱了。这个时候就感觉EF太牛了,那么复杂的linq都能翻译出来,虽然翻译的sql的质量不行,而且还有坑,不过至少翻译出来了,感觉自己永远都没办法做到。

    2、Dapper

    这框架是大名鼎鼎的,性能是相当高的,底层是用EMIT写的。然而我这人就是有技术洁癖,如果是我自己一个人开发,那么如果这个工具让我感觉到了不爽,我就会尝试自己开发。所以我也没用dapper,原因只是它直接操作了Connection,而且要手写sql代码。但是我自己写的在性能上始终是个硬伤,跟dapper比不了。之前我业余用表达式树实现了DataSet到List的转换,然后拿到公司装逼,同事来了一句"来来咱跟dapper比比",我说"得得你就别虐我了成不",比的结果当然是比较惨的,那个时候我觉得我不可能达到dapper的转换速度。

    性能提升测试

    测试代码

    1. static void Main(string[] args)
    2.         {
    3.             EFDbContext db = new EFDbContext();
    4.             db.Configuration.AutoDetectChangesEnabled = false;
    5.             db.Configuration.LazyLoadingEnabled = false;
    6.             db.Configuration.ValidateOnSaveEnabled = false;
    7.             XLinqDataContext xlinq = new XLinqDataContext();
    8.             db.Users.Where(x => false).ToList();//让EF完成初始化
    9.  
    10.  
    11.             ExecuteTimer("EF20万数据查询", () =>
    12.             {
    13.                 db.LargUsers.Take(200000).ToList();
    14.             });
    15.  
    16.             GC.Collect();
    17.             ExecuteTimer("XLinq20万数据查询", () =>
    18.             {
    19.                 var a = xlinq.Set<LargeUser>().Take(200000).ToList();
    20.             });
    21.             GC.Collect();
    22.             ExecuteTimer("Dapper20万数据查询", () =>
    23.             {
    24.                 SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString);
    25.                 var a = conn.Query<LargeUser>("SELECT top 200000 * from dbo.largeusers");
    26.             });
    27.             GC.Collect();
    28.             ExecuteTimer("XLinq50万数据查询", () =>
    29.             {
    30.                 var a = xlinq.Set<LargeUser>().Take(500000).ToList();
    31.             });
    32.             GC.Collect();
    33.             ExecuteTimer("Dapper50万数据查询", () =>
    34.             {
    35.                 SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString);
    36.                 var a = conn.Query<LargeUser>("SELECT top 500000 * from dbo.largeUsers");
    37.             });
    38.             Console.ReadKey();
    39.         }
    40.  
    41.         static void ExecuteTimer(string name, Action action)
    42.         {
    43.             var watch = Stopwatch.StartNew();
    44.             action();
    45.             watch.Stop();
    46.             Console.WriteLine(string.Format("{0}:{1}毫秒", name, watch.ElapsedMilliseconds));
    47.         }

    测试结果

    秒EF是妥妥的,但dapper基本上性能一样,那点差距就直接忽略吧。

    之前也进行过一次,被dapper秒,和EF性能一样,原因是因为字典缓存用的有问题。

    而这次这个最终取数据全部用的数组,一开始其实把dapper都给秒了,快了dapper接近一半,不过后来发现情况没考虑全,考虑全了就差不多了。

    感悟和心得

      1. Expression Tree与EMIT

        应该有不少人都认为后者比前者快,我一开始也这么认为的。

        但园子一位大神说,Expression Tree与EMIT最终生成的代码是一样的,所以性能是一样的。说实在的,我还是不太信。

        然后老赵大神又说,Expression Tree最终也是操作的EMIT来实现的。好吧,其实我有点信了,但还是不太信。

        直到我在写XLinq的时候用纯Expression Tree超越了用纯EMIT的Dapper,我才相信。(不信?可以在评论里要源码)

      2. 基础类型转换的坑

        之前造轮子的时候,一直觉得Convert.ChangeType是尚方宝剑,一剑在手,天下我有。

        然而在写XLinq的时候,被这个方法坑的不轻。

        当我要long、int、short、int?、long?、short?这几个类型相互转换的时候,总会冒出来异常。

        然后谷歌一下,园子大神的博文说:"这个方法一遇到Nullable<T>类型的时候就会挂掉"。

        好吧,我认了(说错了的话请指正)。然后不得不自己处理类型转换,同时也算是知道了这个坑。

      3. LINQ并不能完美实现无缝切换数据库

        一直认为只要linq provider写得足够好,就能支持无缝切换数据库,注意是切换不是支持。

        然而现在看来,想要做到直接改一下配置文件就能切换这是不可能的。

        举一个例子,假如说要将sql server数据库切换到sqlite,这个时候切换过去之后看起来其实不会有什么问题,因为sqlite即使语法不对也可能不会报错。

        在sql server中,2015/1/1这是合法的日期型数据,但这样的数据在sqlite中是无法识别的。

        也就是说,如果你之前用的sql server,并且使用了这样的数据格式,那么切换到sqlite后可能所有关于日期的判断会全部出错,并不是指报异常,而是说计算结果不正确。

      4. 将IDataReader转换到List的功能整个直接生成代码,不再生成每一个属性的委托

        之前在写类似于ORM的工具时,总会将先生成每一个属性的Setter和Getter委托,然后再单独调用这些委托来完成转换。

        这样就几乎无法避免装箱拆箱的问题,但装箱拆箱其实还是会浪费一点性能的。

        后来想到了另一种办法,不再生成Setter和Getter的委托,转而生成包装了一个循环的委托。

        就是说,我传给委托一个类型和一个Reader对象,它就直接返回给我一个List,我不用去获取每一个属性的委托。

        委托中直接生成了访问该类型的每一个属性的代码,这样就直接避免了装箱拆箱,最终性能跟dapper差不多了。

        不过这样做可能会占用多一点缓存,不过现在来说内存应该不是问题

    IDataReader转List关键代码

      1  public static Func<IDataReader, IList> GetDataReaderMapeer(Type type,IDataReader reader)
      2         {
      3             Func<IDataReader, IList> func = null;
      4             if (!_dataReader2ListCahce.TryGetValue(type, out func))
      5             {
      6                 lock (_dataReader2ListCahce)
      7                 {
      8                     if (!_dataReader2ListCahce.TryGetValue(type, out func))
      9                     {
     10                         var readerExp = Expression.Parameter(ReflectorConsts.IDataReaderType, "reader");
     11                         var properties = ExpressionReflector.GetProperties(type);
     12                         var fieldCount = reader.FieldCount;
     13                         var expressions = new List<Expression>();
     14                         var objVar = Expression.Variable(type, "entity");
     15                         var fieldCountVar = Expression.Variable(ReflectorConsts.Int32Type, "fieldCount");
     16                         var readerVar = Expression.Variable(ReflectorConsts.IDataReaderType, "readerVar");
     17                         var propertyNameArr = Expression.Variable(ReflectorConsts.StringArrayType, "pis");
     18                         var indexArrVar = Expression.Variable(ReflectorConsts.Int32ArrayType, "indexes");
     19                         var readIndexVar = Expression.Variable(ReflectorConsts.Int32Type, "readIndex");
     20                         var indexVar = Expression.Variable(ReflectorConsts.Int32Type, "index");
     21                         var forBreakLabel = Expression.Label("forBreak");
     22                         var assignIndexVar = Expression.Assign(indexVar, Expression.Constant(0));
     23                         var listType = ReflectorConsts.ListType.MakeGenericType(type);
     24                         var listVar = Expression.Variable(listType, "list");
     25                         expressions.Add(Expression.Assign(listVar, Expression.New(listType)));
     26                         expressions.Add(Expression.Assign(readerVar, readerExp));
     27                         expressions.Add(assignIndexVar);
     28                         var assignFieldCountVar = Expression.Assign(fieldCountVar,
     29                                 Expression.MakeMemberAccess(readerVar, ReflectorConsts.FieldCountOfIDataReader)
     30                             );
     31                         expressions.Add(assignFieldCountVar);
     32                         var readNameExp = Expression.Call(readerVar, ReflectorConsts.GetOrdinalOfIDataReader, Expression.ArrayIndex(propertyNameArr, indexVar));
     33                         var initIndexArray = Expression.Assign(indexArrVar, Expression.NewArrayBounds(ReflectorConsts.Int32Type, Expression.Constant(fieldCount)));
     34                         var initPropertyArrayExpressions = new List<Expression>();
     35                         for (int i = 0; i < fieldCount; i++)
     36                         {
     37                             initPropertyArrayExpressions.Add(Expression.Constant(reader.GetName(i)));
     38                         }
     39                         var initPropertyArray = Expression.Assign(propertyNameArr, Expression.NewArrayInit(ReflectorConsts.StringType, initPropertyArrayExpressions));
     40                         var assignIndexArrayVar = Expression.Assign(Expression.ArrayAccess(indexArrVar, indexVar), readNameExp);
     41                         expressions.Add(initPropertyArray);
     42                         expressions.Add(initIndexArray);
     43                         expressions.Add(Expression.Loop(
     44                                 Expression.IfThenElse(
     45                                     Expression.LessThan(indexVar, fieldCountVar),
     46                                     Expression.Block(
     47                                         assignIndexArrayVar,
     48                                         Expression.Assign(
     49                                             indexVar,
     50                                             Expression.Add(indexVar, Expression.Constant(1))
     51                                         )
     52                                     ),
     53                                     Expression.Break(forBreakLabel)
     54                                 ),
     55                                 forBreakLabel
     56                             ));
     57                         Expression body = null;
     58                         DataReaderGetMethodSwitcher switcher = null;
     59                         var labelTarget = Expression.Label(type, "return");
     60                         var paramterExpressions = new List<ParameterExpression>();
     61                         var setEntityExpressions = new List<Expression>();
     62                         if (TypeHelper.IsCompilerGenerated(type))
     63                         {
     64                             var constructor = type.GetConstructors().FirstOrDefault();
     65                             if (constructor == null)
     66                             {
     67                                 throw new ArgumentException("类型" + type.FullName + "未找到构造方法");
     68                             }
     69                             var parameters = constructor.GetParameters();
     70                             var expressionParams = new List<ParameterExpression>();
     71                             for (int i = 0; i < fieldCount; i++)
     72                             {
     73                                 var parameter = parameters[i];
     74                                 var parameterVar = Expression.Variable(parameter.ParameterType, parameter.Name);
     75                                 var parameterType = TypeHelper.GetUnderlyingType(parameter.ParameterType);
     76                                 switcher = new DataReaderGetMethodSwitcher(parameterType, readIndexVar, readerVar);
     77                                 switcher.Process();
     78                                 var rightExp = (Expression)switcher.Result;
     79                                 if (TypeHelper.IsNullableType(parameter.ParameterType))
     80                                 {
     81                                     rightExp = Expression.Convert(rightExp, parameter.ParameterType);
     82                                 }
     83                                 var isNullExp = Expression.Call(readerExp, ReflectorConsts.IsDBNullfIDataReader, readIndexVar);
     84                                 var ifExp = Expression.IfThenElse(isNullExp, Expression.Assign(parameterVar, Expression.Default(parameter.ParameterType)), Expression.Assign(parameterVar, rightExp));
     85                                 var exps = new List<Expression>();
     86                                 setEntityExpressions.Add(
     87                                     Expression.Assign(
     88                                         readIndexVar,
     89                                         Expression.ArrayIndex(indexArrVar, Expression.Constant(i))
     90                                     )
     91                                 );
     92                                 setEntityExpressions.Add(ifExp);
     93                                 expressionParams.Add(parameterVar);
     94                             }
     95                             setEntityExpressions.Add(Expression.Assign(objVar, Expression.New(constructor, expressionParams)));
     96                             paramterExpressions.AddRange(expressionParams);
     97                             paramterExpressions.Add(readerVar);
     98                             paramterExpressions.Add(listVar);
     99                         }
    100                         else
    101                         {
    102                             var newExp = Expression.New(type);
    103                             setEntityExpressions.Add(Expression.Assign(objVar, newExp));
    104                             for (int i = 0; i < fieldCount; i++)
    105                             {
    106                                 var propertyName = reader.GetName(i);
    107                                 var property = properties.Get(propertyName);
    108                                 if (property == null)
    109                                 {
    110                                     continue;
    111                                 }
    112                                 var propertyAssignExpressions = new List<Expression>();
    113                                 var propertyExp = Expression.Property(objVar, property);
    114                                 var propertyType = TypeHelper.GetUnderlyingType(property.PropertyType);
    115                                 Expression rightExp = null;
    116                                 switcher = new DataReaderGetMethodSwitcher(propertyType, readIndexVar, readerVar);
    117                                 switcher.Process();
    118                                 rightExp = (Expression)switcher.Result;
    119                                 if (TypeHelper.IsNullableType(property.PropertyType))
    120                                 {
    121                                     rightExp = Expression.Convert(rightExp, property.PropertyType);
    122                                 }
    123                                 setEntityExpressions.Add(
    124                                     Expression.Assign(
    125                                         readIndexVar,
    126                                         Expression.ArrayIndex(indexArrVar, Expression.Constant(i))
    127                                     )
    128                                 );
    129                                 var ifExp = Expression.IfThen(
    130                                     Expression.Not(
    131                                         Expression.Call(readerExp, ReflectorConsts.IsDBNullfIDataReader, readIndexVar)
    132                                     ),
    133                                     Expression.Assign(propertyExp, rightExp)
    134                                 );
    135                                 setEntityExpressions.Add(ifExp);
    136                             }
    137                             paramterExpressions.Add(listVar);
    138                             paramterExpressions.Add(readerVar);
    139                         }
    140                         paramterExpressions.Add(indexVar);
    141                         paramterExpressions.Add(propertyNameArr);
    142                         paramterExpressions.Add(fieldCountVar);
    143                         paramterExpressions.Add(indexArrVar);
    144                         paramterExpressions.Add(readIndexVar);
    145                         //expressions.Add(Expression.Call(
    146                         //        null,
    147                         //        typeof(MessageBox).GetMethod("Show", new Type[] { ReflectorConsts.StringType }),
    148                         //    Expression.Call(
    149                         //        null,
    150                         //        ReflectorConsts.ConvertToStringMethod,
    151                         //        Expression.Convert(
    152                         //            Expression.ArrayIndex(indexArrVar, Expression.Constant(1)),
    153                         //            ReflectorConsts.ObjectType)
    154                         //        )));
    155                         setEntityExpressions.Add(Expression.Call(listVar, listType.GetMethods().FirstOrDefault(x => x.Name == "Add"), objVar));
    156                         expressions.Add(
    157                             Expression.Loop(
    158                                 Expression.Block(
    159                                     Expression.IfThenElse(
    160                                         Expression.Call(readerVar, ReflectorConsts.ReadOfIDataReader),
    161                                         Expression.Block(new[] { objVar }, setEntityExpressions),
    162                                         Expression.Break(labelTarget, Expression.Default(type))
    163                                     )
    164                                 ),
    165                                 labelTarget
    166                             ));
    167                         expressions.Add(listVar);
    168                         body = Expression.Block(
    169                             paramterExpressions,
    170                             expressions
    171                         );
    172                         func = Expression.Lambda<Func<IDataReader, IList>>(body, readerExp).Compile();
    173                         _dataReader2ListCahce.Add(type, func);
    174                     }
    175                 }
    176             }
    177             return func;
    178         }
    View Code
  • 相关阅读:
    scrapy 知乎用户信息爬虫
    快读模板&&快出模板
    洛谷P7078 贪吃蛇
    CSP2020-S1总结
    洛谷P1736 创意吃鱼法
    luogu P3004 [USACO10DEC]宝箱Treasure Chest
    Markdown与LaTeX
    洛谷P2197 【模板】nim游戏
    洛谷CF1360H Binary Median
    洛谷P1063 能量项链
  • 原文地址:https://www.cnblogs.com/wzxinchen/p/4639046.html
Copyright © 2011-2022 走看看