It's easy to call a method, just call the code, and everyone will. Second, you can also use reflection. But the performance of the call through reflection is much lower than the direct call-at least in absolute terms. While this is a well-known phenomenon, let's write a procedure to verify it. For example, let's create a new console application and write the simplest call method.
class program{ staticvoid Main (string[] args) { } publicvoid Call (objectObjectobject O3) {}}
The call method accepts three object parameters without any implementation, so that we can let the test focus on the method invocation rather than the method implementation itself. So we started writing the test code to compare the performance gap between the method's direct call and the reflection call:
Static voidMain (string[] args) { intTimes =1000000; Program Program=NewProgram (); Object[] Parameters =New Object[] {New Object(),New Object(),New Object() }; Program. Call (NULL,NULL,NULL);//Force Jit-compileStopwatch Watch1=NewStopwatch (); Watch1. Start (); for(inti =0; I < times; i++) {program. Call (parameters[0], parameters[1], parameters[2]); } watch1. Stop (); Console.WriteLine (Watch1. Elapsed+"(Directly Invoke)"); MethodInfo MethodInfo=typeof(program). GetMethod ("Pager"); Stopwatch WATCH2=NewStopwatch (); Watch2. Start (); for(inti =0; I < times; i++) {Methodinfo.invoke (program, parameters); } watch2. Stop (); Console.WriteLine (WATCH2. Elapsed+"(Reflection Invoke)"); Console.WriteLine ("Press any key to continue ..."); Console.readkey ();}
The results of the implementation are as follows:
xx:00.0119041 (Directly invoke), XX:04.5527141 continue...
By the time spent on each call 1 million times, the two have an order of magnitude difference in performance. As a result, many frameworks will try to use some of the more advanced alternatives to improve performance in scenarios where reflection must be used. For example, use the CodeDom to generate code and compile dynamically, or use emit to write IL directly. But since. NET 3.5 has released new features related to expression, we have a more convenient and intuitive solution in the above scenario.
Friends who understand Expression-related features may know,system.linq.expressions.expression<tdelegate> An object of type will get a delegate object of type TDelegate after it has been called compile method, while calling a delegate object is similar to the performance cost of calling a method directly. So what kind of delegate object should we get for the above situation? To make the solution generic enough, we must unify the various signature methods into the same delegate type, as follows:
Publicfunc<Object,Object[],Object>getvoiddelegate () {Expression<Action<Object,Object[]>> exp = (instance, parameters) =(program) instance). Call (parameters[0], parameters[1], parameters[2]); Action<Object,Object[]> action =Exp.compile (); return(instance, parameters) ={Action (instance, parameters); return NULL; };}
As above, we get a func<object, object[], object> type of delegate, which means it accepts an object type with an argument of type object[], and returns the result of an object type--and so on, Have friends found that this signature is exactly the same as the MethodInfo type of Invoke method? Thankfully, however, we are now invoking this delegate much more than it is called by reflection. So what about a method that has a return value? It is more convenient to construct a delegate object:
Public intCall (ObjectO1,ObjectO2) {return 0; } Publicfunc<Object,Object[],Object>getdelegate () {Expression<Func<Object,Object[],Object>> exp = (instance, parameters) = =(program) instance). Call (parameters[0], parameters[1]); returnexp.compile ();}
At this point, I think my friends have been able to easily get a delegate constructor that calls a static method. It can be seen that the key to this solution is to construct a suitable expression<tdelegate>, so we will now write a Dynamicexecuter class as a more complete solution:
Public classdynamicmethodexecutor{Privatefunc<Object,Object[],Object>M_execute; Publicdynamicmethodexecutor (MethodInfo MethodInfo) { This. M_execute = This. Getexecutedelegate (MethodInfo); } Public ObjectExecute (ObjectInstanceObject[] Parameters) { return This. M_execute (instance, parameters); } Privatefunc<Object,Object[],Object>getexecutedelegate (MethodInfo MethodInfo) {//parameters to executeParameterExpression Instanceparameter =Expression.parameter (typeof(Object),"instance"); ParameterExpression Parametersparameter=Expression.parameter (typeof(Object[]),"Parameters"); //Build parameter listList<expression> parameterexpressions =NewList<expression>(); Parameterinfo[] Paraminfos=methodinfo.getparameters (); for(inti =0; i < paraminfos.length; i++) { //(Ti) parameters[i]Binaryexpression valueobj =Expression.arrayindex (Parametersparameter, Expression.constant (i)); Unaryexpression Valuecast=Expression.convert (Valueobj, Paraminfos[i]. ParameterType); Parameterexpressions.add (Valuecast); } //non-instance for static method, or ((tinstance) instance)Expression instancecast = methodinfo.isstatic?NULL: Expression.convert (Instanceparameter, Methodinfo.reflectedtype); //static Invoke or ((tinstance) instance). MethodMethodcallexpression Methodcall =Expression.call (Instancecast, MethodInfo, parameterexpressions); //((tinstance) instance). Method ((T0) parameters[0], (T1) parameters[1], ...) if(Methodcall.type = =typeof(void) {Expression<Action<Object,Object[]>> lambda =Expression.lambda<Action<Object,Object[]>>(Methodcall, Instanceparameter, Parametersparameter); Action<Object,Object[]> Execute =Lambda.compile (); return(instance, parameters) ={Execute (instance, parameters); return NULL; }; } Else{unaryexpression Castmethodcall=Expression.convert (Methodcall,typeof(Object)); Expression<Func<Object,Object[],Object>> lambda =Expression.lambda<Func<Object,Object[],Object>>(Castmethodcall, Instanceparameter, Parametersparameter); returnLambda.compile (); } }}
The key to Dynamicmethodexecutor is to construct the logic of expression tree in the Getexecutedelegate method. If you are not familiar with the structure of an expression tree, try using expression tree Visualizer to observe and analyze a ready-made expression tree. After we pass a MethodInfo object into the Dynamicmethodexecutor constructor, we can pass the different sets of instance objects and parameter object arrays into execute for execution. All of this is like using reflection to make calls, but it has a noticeable improvement in performance. For example, we add more test code:
Newnew Stopwatch (); Watch3. Start (); for (int0; i < times; i++) { " (Dynamic executor)");
The results of the current implementation are:
xx:00.0125539 (Directly invoke), XX:04.5349626 (Reflection invoke):xx:00.0322555continue...
In fact, the compile method of the,expression<tdelegate> type uses emit to generate the delegate object. But now we don't have to look at the lower end of the IL, as long as the use of high-end API to the expression tree structure, this is undoubtedly a progress. However, this approach also has some limitations, such as we can only call public methods, and contains the method of the Out/ref parameter, or in addition to the method of other types of members, we can not be as easy as the example of writing code.
Supplement
Wooden Fox in the comments quoted in the Code Project article "A General Fast Method Invoker", wherein through emit constructs the Fastinvokehandler delegate object (its signature and Func<object, Object[], object> is more efficient than the "method direct" invocation, although it does not appear to be the case from the original example. In fact, Fastinvokehandler's internal implementation is exactly the same as the dynamicmethodexecutor, and it's amazing to have such incredible performance. I suspect that the performance advantages of Fastinvokehandler and dynamicmethodexecutor may be reflected in the following areas:
- The execution performance of a generic delegate type is slightly lower than the non-generic delegate type (verification).
- One more Execute method call, which loses some of the performance.
- The resulting IL code is shorter and more compact.
- The wooden Fox Brothers did not compile with release mode. :P
I'm not sure if there are any friends who are interested in doing another test, but please note that such performance tests must be done under release compilation (which is easily overlooked), otherwise the meaning is not much.
In addition, I would like to emphasize that this article is a purely technical comparison, not to guide the pursuit of a bit of performance optimization. Sometimes I see some articles about comparing for or foreach performance to make a lot of friends tangled with this, even in the red, I always feel a little helpless. In fact, in theory, to improve the performance of the way there are many, remember at that time in the University of Learning Introduction to computer system This course has a job is for a C program for performance optimization, at that time used a lot of means, For example, inline method calls to reduce CPU instruction calls, adjust loop nesting order to increase CPU cache hit ratios, use inline ASM replacements for some code, and so on, are "fair bet", and everyone is Dmitri Trenin cheering for the performance improvement of several clock cycles ...
It's a theory, it's learning. But in the practical application, we must also correctly treat the learned theoretical knowledge. I often say: "Any application will have its performance bottleneck, only from the performance bottleneck to achieve a multiplier effect." "For example, the performance bottlenecks of ordinary Web applications tend to be in external IO (especially database reading and writing), so it is important to start with a real performance improvement (e.g. database tuning, better caching design). Because of this, the key to developing a high-performance Web application is not in a language or language environment, and. NET, RoR, PHP, Java, and so on, are doing well in this area.
The direct invocation of the method, the reflection call