重復造輪子感悟 – 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM