Take another look at ExpressionTree, Emit, and performance comparison of reflection creation objects

[preface]

A few days ago, I had a whim to study how to build a Spring framework, which naturally involves the creation of Ioc container objects, and how to create an object with high performance. First, I thought of Emit and wrote a factory for creating objects by Emit. During the performance test, I found that it is actually better than the reflection activator The createinstance method has no advantage in creating objects. Then I wrote an object factory of Expression Tree, and found that it was on a par with Emit and still had no advantage over the system reflection method.

I checked the research of the great gods in the park for the first time, for example:

  Leven Of explore. net object, query the performance comparison between the object created by Activator.CreateInstance(Type type) method and the object created by Expression Tree

  Will Meng Of Let's talk about activator Performance comparison between createinstance (type type) method and Expression Tree (updated version)

After a detailed comparison, it is found that the comparison of the above-mentioned leaders is the performance comparison made with the nonparametric constructor.

Therefore, I also wrote a Demo of the nonparametric constructor with Expression Tree. By comparison, I found that the implementation of the nonparametric constructor Expression Tree is indeed better than the reflection activator The createinstance method has much higher performance. However, if you want to be compatible with the creation of objects with parameters, it takes a lot of time in parameter judgment and method caching, and its performance is not better than that of direct reflection calls. The test code is released below, and you are welcome to discuss it with bloggers.

[implementation function]

We want to implement a factory for creating objects.

The new object, the Expression Tree implementation (parameter / no parameter), and the Emit+Delegate implementation are compared.

[implementation process]

Objects ready for testing:

Prepare two classes, ClassA and ClassB, where ClassA has parameter construction of ClassB and ClassB has no parameter construction.

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. The simplest way to create an object in the Expression Tree method without considering parameters (no constructor with parameters)

 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. Create objects in the Expression Tree mode with parameter processing (with parameter constructor and local cache for parameter delegate)

 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                 //From the parameter expression (the parameter is: object[])Extract parameters from
20                 var arg = BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i));
21                 //Convert parameter to specified type
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                 //establish lambda Arguments to expressions
46                 var lambdaParam = Expression.Parameter(typeof(object[]), "_args");
47 
48                 //Create an array of parameter expressions for the constructor
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. Create objects in the Emit+Delegate mode with parameter processing (with parameter constructor and local cache for parameter 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 }

[system test]

We write unit test code to test the performance of the above code segments:

  1. Unit test of nonparametric constructor

 1 [Theory]
 2 [InlineData(1000000)]
 3 [Trait("description", "Performance comparison of method calls in parameterless construction")]
 4 public void PerformanceReportWithNoArguments(int count)
 5 {
 6     Trace.WriteLine($"#{count} calls:");
 7 
 8     double time = StopwatchHelper.Caculate(count, () =>
 9     {
10         ClassB b = new ClassB();
11     }).TotalMilliseconds;
12     Trace.WriteLine($"'New'time consuming {time} milliseconds");
13 
14     double time2 = StopwatchHelper.Caculate(count, () =>
15     {
16         ClassB b = CreateObjectFactory.CreateInstance<ClassB>();
17     }).TotalMilliseconds;
18     Trace.WriteLine($"'Emit factory'time consuming {time2} milliseconds");
19 
20     double time3 = StopwatchHelper.Caculate(count, () =>
21     {
22         ClassB b = ExpressionCreateObject.CreateInstance<ClassB>();
23     }).TotalMilliseconds;
24     Trace.WriteLine($"'Expression'time consuming {time3} milliseconds");
25 
26     double time4 = StopwatchHelper.Caculate(count, () =>
27     {
28         ClassB b = ExpressionCreateObjectFactory.CreateInstance<ClassB>();
29     }).TotalMilliseconds;
30     Trace.WriteLine($"'Expression factory'time consuming {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'time consuming {time5} milliseconds");
38 
39 
40     /**
41               #1000000 Calls:
42                 'New'Time consuming 21.7474 milliseconds
43                 'Emit 174.088 milliseconds
44                 'Expression'Time consuming 42.9405 milliseconds
45                 'Expression 162.548 milliseconds
46                 'Activator.CreateInstance'Time consuming 67.3712 milliseconds
47              * */
48 }

  

It can be seen from the above code test that, compared with the direct New object, the implementation method of Expression without parameters has the highest performance, and is better than the system reflection activator The method performance of createinstance should be high.

The implementation of Emit without parameters is not provided here. Based on the results of this performance test, it is estimated that the performance of the implementation of Emit without parameters will be higher than the performance reflected by the system.

  2. Unit test with parameter constructor

 1 [Theory]
 2 [InlineData(1000000)]
 3 [Trait("description", "Comparison of call performance of methods with parameters")]
 4 public void PerformanceReportWithArguments(int count)
 5 {
 6     Trace.WriteLine($"#{count} calls:");
 7 
 8     double time = StopwatchHelper.Caculate(count, () =>
 9     {
10         ClassA a = new ClassA(new ClassB());
11     }).TotalMilliseconds;
12     Trace.WriteLine($"'New'time consuming {time} milliseconds");
13 
14     double time2 = StopwatchHelper.Caculate(count, () =>
15     {
16         ClassA a = CreateObjectFactory.CreateInstance<ClassA>(new ClassB());
17     }).TotalMilliseconds;
18     Trace.WriteLine($"'Emit factory'time consuming {time2} milliseconds");
19 
20     double time4 = StopwatchHelper.Caculate(count, () =>
21     {
22         ClassA a = ExpressionCreateObjectFactory.CreateInstance<ClassA>(new ClassB());
23     }).TotalMilliseconds;
24     Trace.WriteLine($"'Expression factory'time consuming {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'time consuming {time5} milliseconds");
31 
32 
33     /**
34       #1000000 Calls:
35         'New'Time consuming 29.3612 milliseconds
36         'Emit 634.2714 milliseconds
37         'Expression Plant 'takes 620.2489 milliseconds
38         'Activator.CreateInstance'588.0409 milliseconds
39      * */
40 }

   

It can be seen from the above code test that the system reflects activator Createinstance has the highest performance, while the implementation methods of Emit and ExpressionTree are inferior.

[summary]

Through the test of this article, we have a new understanding of the performance of creating objects by reflection In the lower version of netframework, the performance of reflection is not as high as it is now. However, after Microsoft's iterative upgrade, the performance of reflection calls in the latest version is relatively objective, especially for the creation of objects with parameter constructors. There is an opportunity to make a detailed analysis of the internal implementation.

No matter whether the parameterized construction is implemented by Expression Tree cache delegation or Emit directly, no additional judgment is required and reflection is not used. It is predictable that the performance is higher than the system reflection. However, after the judgment of various parameters and caching for different parameter implementation methods are added, the performance is reflected and exceeded. Because the judgment of parameters, the generation of keys during caching, and the judgment of stored Key values of Map sets are time-consuming, they are not better than reflection.

The performance bottleneck of the system is often not the loss of reflecting or not reflecting the methods of creating objects. After testing, it can be found that even if reflection is used to create, millions of calls take less than 1s, but millions of system calls often take a long time. The purpose of our testing is only to explore. In the implementation of the framework, we will focus on the ease of use of the framework, Fault tolerance, etc.

Statement: I don't have any doubts about the leaders in the park. Personally, I think it is just a supplement to the previous test cases. If there are any objections or optimization to the test process, please stir up waves in the comment area ~~~

[source code address]

Source code address of this article: https://github.com/sevenTiny/SevenTiny.Bantina/blob/master/10-Code/Test.SevenTiny.Bantina/CreateObjectFactoryTest.cs

Or view the project directly from the clone Code: https://github.com/sevenTiny/SevenTiny.Bantina

  

 

source: https://www.cnblogs.com/7tiny/p/9861166.html

Tags: ioc

Posted by hkothari on Thu, 02 Jun 2022 01:35:30 +0530