再看ExpressionTree,Emit,反射創建對象性能對比
【前言】
前幾日心血來潮想研究著做一個Spring框架,自然地就涉及到了Ioc容器對象創建的問題,研究怎麽高性能地創建一個對象。第一聯想到了Emit,興致沖沖寫了個Emit創建對象的工廠。在做性能測試的時候,發現居然比反射Activator.CreateInstance方法創建對象毫無優勢可言。繼而又寫了個Expression Tree的對象工廠,發現和Emit不相上下,比起系統反射方法仍然無優勢可言。
第一時間查看了園內大神們的研究,例如:
Leven 的 探究.net對象的創建,質疑《再談Activator.CreateInstance(Type type)方法創建對象和Expression Tree創建對象性能的比較》
Will Meng 的 再談Activator.CreateInstance(Type type)方法創建對象和Expression Tree創建對象性能的比較(更新版)
詳細對比了後發現,上述大佬們的對比都是使用無參構造函數做的性能對比。
於是,我也用Expression Tree寫了個無參構造函數的Demo,對比之下發現,無參構造Expression Tree實現方式確實比反射Activator.CreateInstance方法性能要高很多,但是如果想要兼容帶參的對象創建,在參數判斷,方法緩存上來說,耗費了很多的時間,性能並不比直接反射調用好,下面放出測試的代碼,歡迎博友探討,雅正。
【實現功能】
我們要實現一個創建對象的工廠。
new對象,Expression Tree實現(參數/不考慮參數),Emit+Delegate(考慮參數)實現方式做對比。
【實現過程】
準備好測試的對象:
準備兩個類,ClassA,ClassB,其中ClassA有ClassB的參數構造,ClassB無參構造。
1 public class ClassA 2 { 3 public ClassA(ClassB classB) { } 4 public int GetInt() => default(int); 5 } 6 publicclass ClassB 7 { 8 public int GetInt() => default(int); 9 }
1.最簡單不考慮參數的 Expression Tree方式創建對象(無帶參構造函數)
1 public class ExpressionCreateObject 2 { 3 private static Func<object> func; 4 public static T CreateInstance<T>() where T : class 5 { 6 if (func == null) 7 { 8 var newExpression = Expression.New(typeof(T)); 9 func = Expression.Lambda<Func<object>>(newExpression).Compile(); 10 } 11 return func() as T; 12 } 13 }
2.有參數處理的Expression Tree方式創建對象(帶參構造函數,且針對參數的委托進行了本地緩存)
1 public class ExpressionCreateObjectFactory 2 { 3 private static Dictionary<string, Func<object[], object>> funcDic = new Dictionary<string, Func<object[], object>>(); 4 public static T CreateInstance<T>() where T : class 5 { 6 return CreateInstance(typeof(T), null) as T; 7 } 8 9 public static T CreateInstance<T>(params object[] parameters) where T : class 10 { 11 return CreateInstance(typeof(T), parameters) as T; 12 } 13 14 static Expression[] buildParameters(Type[] parameterTypes, ParameterExpression paramExp) 15 { 16 List<Expression> list = new List<Expression>(); 17 for (int i = 0; i < parameterTypes.Length; i++) 18 { 19 //從參數表達式(參數是:object[])中取出參數 20 var arg = BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i)); 21 //把參數轉化成指定類型 22 var argCast = Expression.Convert(arg, parameterTypes[i]); 23 24 list.Add(argCast); 25 } 26 return list.ToArray(); 27 } 28 29 public static object CreateInstance(Type instanceType, params object[] parameters) 30 { 31 32 Type[] ptypes = new Type[0]; 33 string key = instanceType.FullName; 34 35 if (parameters != null && parameters.Any()) 36 { 37 ptypes = parameters.Select(t => t.GetType()).ToArray(); 38 key = string.Concat(key, "_", string.Concat(ptypes.Select(t => t.Name))); 39 } 40 41 if (!funcDic.ContainsKey(key)) 42 { 43 ConstructorInfo constructorInfo = instanceType.GetConstructor(ptypes); 44 45 //創建lambda表達式的參數 46 var lambdaParam = Expression.Parameter(typeof(object[]), "_args"); 47 48 //創建構造函數的參數表達式數組 49 var constructorParam = buildParameters(ptypes, lambdaParam); 50 51 var newExpression = Expression.New(constructorInfo, constructorParam); 52 53 funcDic.Add(key, Expression.Lambda<Func<object[], object>>(newExpression, lambdaParam).Compile()); 54 } 55 return funcDic[key](parameters); 56 } 57 }
3.有參數處理的 Emit+Delegate 方式創建對象(帶參構造函數,且針對參數Delegate本地緩存)
1 namespace SevenTiny.Bantina 2 { 3 internal delegate object CreateInstanceHandler(object[] parameters); 4 5 public class CreateObjectFactory 6 { 7 static Dictionary<string, CreateInstanceHandler> mHandlers = new Dictionary<string, CreateInstanceHandler>(); 8 9 public static T CreateInstance<T>() where T : class 10 { 11 return CreateInstance<T>(null); 12 } 13 14 public static T CreateInstance<T>(params object[] parameters) where T : class 15 { 16 return (T)CreateInstance(typeof(T), parameters); 17 } 18 19 public static object CreateInstance(Type instanceType, params object[] parameters) 20 { 21 Type[] ptypes = new Type[0]; 22 string key = instanceType.FullName; 23 24 if (parameters != null && parameters.Any()) 25 { 26 ptypes = parameters.Select(t => t.GetType()).ToArray(); 27 key = string.Concat(key, "_", string.Concat(ptypes.Select(t => t.Name))); 28 } 29 30 if (!mHandlers.ContainsKey(key)) 31 { 32 CreateHandler(instanceType, key, ptypes); 33 } 34 return mHandlers[key](parameters); 35 } 36 37 static void CreateHandler(Type objtype, string key, Type[] ptypes) 38 { 39 lock (typeof(CreateObjectFactory)) 40 { 41 if (!mHandlers.ContainsKey(key)) 42 { 43 DynamicMethod dm = new DynamicMethod(key, typeof(object), new Type[] { typeof(object[]) }, typeof(CreateObjectFactory).Module); 44 ILGenerator il = dm.GetILGenerator(); 45 ConstructorInfo cons = objtype.GetConstructor(ptypes); 46 47 if (cons == null) 48 { 49 throw new MissingMethodException("The constructor for the corresponding parameter was not found"); 50 } 51 52 il.Emit(OpCodes.Nop); 53 54 for (int i = 0; i < ptypes.Length; i++) 55 { 56 il.Emit(OpCodes.Ldarg_0); 57 il.Emit(OpCodes.Ldc_I4, i); 58 il.Emit(OpCodes.Ldelem_Ref); 59 if (ptypes[i].IsValueType) 60 il.Emit(OpCodes.Unbox_Any, ptypes[i]); 61 else 62 il.Emit(OpCodes.Castclass, ptypes[i]); 63 } 64 65 il.Emit(OpCodes.Newobj, cons); 66 il.Emit(OpCodes.Ret); 67 CreateInstanceHandler ci = (CreateInstanceHandler)dm.CreateDelegate(typeof(CreateInstanceHandler)); 68 mHandlers.Add(key, ci); 69 } 70 } 71 } 72 } 73 }
【系統測試】
我們編寫單元測試代碼對上述幾個代碼段進行性能測試:
1.無參構造函數的單元測試
1 [Theory] 2 [InlineData(1000000)] 3 [Trait("description", "無參構造各方法調用性能對比")] 4 public void PerformanceReportWithNoArguments(int count) 5 { 6 Trace.WriteLine($"#{count} 次調用:"); 7 8 double time = StopwatchHelper.Caculate(count, () => 9 { 10 ClassB b = new ClassB(); 11 }).TotalMilliseconds; 12 Trace.WriteLine($"‘New’耗時 {time} milliseconds"); 13 14 double time2 = StopwatchHelper.Caculate(count, () => 15 { 16 ClassB b = CreateObjectFactory.CreateInstance<ClassB>(); 17 }).TotalMilliseconds; 18 Trace.WriteLine($"‘Emit 工廠’耗時 {time2} milliseconds"); 19 20 double time3 = StopwatchHelper.Caculate(count, () => 21 { 22 ClassB b = ExpressionCreateObject.CreateInstance<ClassB>(); 23 }).TotalMilliseconds; 24 Trace.WriteLine($"‘Expression’耗時 {time3} milliseconds"); 25 26 double time4 = StopwatchHelper.Caculate(count, () => 27 { 28 ClassB b = ExpressionCreateObjectFactory.CreateInstance<ClassB>(); 29 }).TotalMilliseconds; 30 Trace.WriteLine($"‘Expression 工廠’耗時 {time4} milliseconds"); 31 32 double time5 = StopwatchHelper.Caculate(count, () => 33 { 34 ClassB b = Activator.CreateInstance<ClassB>(); 35 //ClassB b = Activator.CreateInstance(typeof(ClassB)) as ClassB; 36 }).TotalMilliseconds; 37 Trace.WriteLine($"‘Activator.CreateInstance’耗時 {time5} milliseconds"); 38 39 40 /** 41 #1000000 次調用: 42 ‘New’耗時 21.7474 milliseconds 43 ‘Emit 工廠’耗時 174.088 milliseconds 44 ‘Expression’耗時 42.9405 milliseconds 45 ‘Expression 工廠’耗時 162.548 milliseconds 46 ‘Activator.CreateInstance’耗時 67.3712 milliseconds 47 * */ 48 }
通過上面代碼測試可以看出,100萬次調用,相比直接New對象,Expression無參數考慮的實現方式性能最高,比系統反射Activator.CreateInstance的方法性能要高。
這裏沒有提供Emit無參的方式實現,看這個性能測試的結果,預估Emit無參的實現方式性能會比系統反射的性能要高的。
2.帶參構造函數的單元測試
1 [Theory] 2 [InlineData(1000000)] 3 [Trait("description", "帶參構造各方法調用性能對比")] 4 public void PerformanceReportWithArguments(int count) 5 { 6 Trace.WriteLine($"#{count} 次調用:"); 7 8 double time = StopwatchHelper.Caculate(count, () => 9 { 10 ClassA a = new ClassA(new ClassB()); 11 }).TotalMilliseconds; 12 Trace.WriteLine($"‘New’耗時 {time} milliseconds"); 13 14 double time2 = StopwatchHelper.Caculate(count, () => 15 { 16 ClassA a = CreateObjectFactory.CreateInstance<ClassA>(new ClassB()); 17 }).TotalMilliseconds; 18 Trace.WriteLine($"‘Emit 工廠’耗時 {time2} milliseconds"); 19 20 double time4 = StopwatchHelper.Caculate(count, () => 21 { 22 ClassA a = ExpressionCreateObjectFactory.CreateInstance<ClassA>(new ClassB()); 23 }).TotalMilliseconds; 24 Trace.WriteLine($"‘Expression 工廠’耗時 {time4} milliseconds"); 25 26 double time5 = StopwatchHelper.Caculate(count, () => 27 { 28 ClassA a = Activator.CreateInstance(typeof(ClassA), new ClassB()) as ClassA; 29 }).TotalMilliseconds; 30 Trace.WriteLine($"‘Activator.CreateInstance’耗時 {time5} milliseconds"); 31 32 33 /** 34 #1000000 次調用: 35 ‘New’耗時 29.3612 milliseconds 36 ‘Emit 工廠’耗時 634.2714 milliseconds 37 ‘Expression 工廠’耗時 620.2489 milliseconds 38 ‘Activator.CreateInstance’耗時 588.0409 milliseconds 39 * */ 40 }
通過上面代碼測試可以看出,100萬次調用,相比直接New對象,系統反射Activator.CreateInstance的方法性能最高,而Emit實現和ExpressionTree的實現方法就要遜色一籌。
【總結】
通過本文的測試,對反射創建對象的性能有了重新的認識,在.netframework低版本中,反射的性能是沒有現在這麽高的,但是經過微軟的叠代升級,目前最新版本的反射調用性能還是比較客觀的,尤其是突出在了針對帶參數構造函數的對象創建上,有機會對內部實現做詳細分析。
無參構造無論是采用Expression Tree緩存委托還是Emit直接實現,都無需額外的判斷,也並未使用反射,性能比系統反射要高是可以預見到的。但是加入了各種參數的判斷以及針對不同參數的實現方式的緩存之後,性能卻被反射反超,因為參數的判斷以及緩存時Key的生成,Map集合的存儲鍵值判斷等都是有耗時的,綜合下來,並不比反射好。
系統的性能瓶頸往往並不在反射或者不反射這些創建對象方法的損耗上,經過測試可以發現,即便使用反射創建,百萬次的調用耗時也不到1s,但是百萬次的系統調用往往耗時是比較長的,我們做測試的目的僅僅是為了探索,具體在框架的實現中,會著重考慮框架的易用性,容錯性等更為關鍵的部分。
聲明:並不是對園內大佬有啥質疑,個人認為僅僅是對以往測試的一種測試用例的補充,如果對測試過程有任何異議或者優化的部分,歡迎評論區激起波濤~!~~
【源碼地址】
本文源代碼地址:https://github.com/sevenTiny/SevenTiny.Bantina/blob/master/10-Code/Test.SevenTiny.Bantina/CreateObjectFactoryTest.cs
或者直接clone代碼查看項目:https://github.com/sevenTiny/SevenTiny.Bantina
再看ExpressionTree,Emit,反射創建對象性能對比