再看ExpressionTree,Emit,反射建立對象性能對比

【前言】

  前幾日心血來潮想研究着作一個Spring框架,天然地就涉及到了Ioc容器對象建立的問題,研究怎麼高性能地建立一個對象。第一聯想到了Emit,興致沖沖寫了個Emit建立對象的工廠。在作性能測試的時候,發現竟然比反射Activator.CreateInstance方法建立對象毫無優點可言。繼而又寫了個Expression Tree的對象工廠,發現和Emit不相上下,比起系統反射方法仍然無優點可言。html

  第一時間查看了園內大神們的研究,例如:git

  Leven 的 探究.net對象的建立,質疑《再談Activator.CreateInstance(Type type)方法建立對象和Expression Tree建立對象性能的比較》github

  Will Meng 的 再談Activator.CreateInstance(Type type)方法建立對象和Expression Tree建立對象性能的比較(更新版)數組

  詳細對比了後發現,上述大佬們的對比都是使用無參構造函數作的性能對比緩存

  因而,我也用Expression Tree寫了個無參構造函數的Demo,對比之下發現,無參構造Expression Tree實現方式確實比反射Activator.CreateInstance方法性能要高不少,可是若是想要兼容帶參的對象建立,在參數判斷,方法緩存上來講,耗費了不少的時間,性能並不比直接反射調用好,下面放出測試的代碼,歡迎博友探討,雅正。框架

【實現功能】

  咱們要實現一個建立對象的工廠。函數

  new對象,Expression Tree實現(參數/不考慮參數),Emit+Delegate(考慮參數)實現方式作對比。post

【實現過程】

  準備好測試的對象:性能

  準備兩個類,ClassA,ClassB,其中ClassA有ClassB的參數構造,ClassB無參構造。單元測試

1 public class ClassA
2 {
3     public ClassA(ClassB classB) { }
4     public int GetInt() => default(int);
5 }
6 public class 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

  

相關文章
相關標籤/搜索