.NET Dynamic Method. Best performance

What is the best way to create a dynamic method on the fly, but is it equally effective if it was compiled in VS?

Say I want to create a calculator. The user input formula says A + B / C * 0.5;

I want to be able to create something like Func that will take A, B, C as double parameters and return double.

Parameter type and return type always double. The number of parameters is variable, but not less than one.

These formulas can often be changed / added. Once the formula is β€œcompiled”, it will be part of the low-latency code that can be called 1000 times / sec.

I need to find a simple and reliable way to create it, but I must have the exact performance characteristics of a statically constructed and optimized method .

+3
source share
4 answers

I found a Microsoft blog on this ( Generating Dynamic Methods ) and compared performance between a static method, a compiled expression tree, and IL injection.

Here is the code:

static void Main(string[] args) { double acc = 0; var il = ILFact(); il.Invoke(1); var et = ETFact(); et(1); Stopwatch sw = new Stopwatch(); for (int k = 0; k < 10; k++) { long time1, time2; sw.Restart(); for (int i = 0; i < 30000; i++) { var result = CSharpFact(i); acc += result; } sw.Stop(); time1 = sw.ElapsedMilliseconds; sw.Restart(); for (int i = 0; i < 30000; i++) { double result = il.Invoke(i); acc += result; } sw.Stop(); time2 = sw.ElapsedMilliseconds; sw.Restart(); for (int i = 0; i < 30000; i++) { var result = et(i); acc += result; } sw.Stop(); Console.WriteLine("{0,6} {1,6} {2,6}", time1, time2, sw.ElapsedMilliseconds); } Console.WriteLine("\n{0}...\n", acc); Console.ReadLine(); } static Func<int, int> ILFact() { var method = new DynamicMethod( "factorial", typeof(int), new[] { typeof(int) } ); var il = method.GetILGenerator(); var result = il.DeclareLocal(typeof(int)); var startWhile = il.DefineLabel(); var returnResult = il.DefineLabel(); // result = 1 il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Stloc, result); // if (value <= 1) branch end il.MarkLabel(startWhile); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Ble_S, returnResult); // result *= (value--) il.Emit(OpCodes.Ldloc, result); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Sub); il.Emit(OpCodes.Starg_S, 0); il.Emit(OpCodes.Mul); il.Emit(OpCodes.Stloc, result); // end while il.Emit(OpCodes.Br_S, startWhile); // return result il.MarkLabel(returnResult); il.Emit(OpCodes.Ldloc, result); il.Emit(OpCodes.Ret); return (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>)); } static Func<int, int> ETFact() { // Creating a parameter expression. ParameterExpression value = Expression.Parameter(typeof(int), "value"); // Creating an expression to hold a local variable. ParameterExpression result = Expression.Parameter(typeof(int), "result"); // Creating a label to jump to from a loop. LabelTarget label = Expression.Label(typeof(int)); // Creating a method body. BlockExpression block = Expression.Block( // Adding a local variable. new[] { result }, // Assigning a constant to a local variable: result = 1 Expression.Assign(result, Expression.Constant(1)), // Adding a loop. Expression.Loop( // Adding a conditional block into the loop. Expression.IfThenElse( // Condition: value > 1 Expression.GreaterThan(value, Expression.Constant(1)), // If true: result *= value -- Expression.MultiplyAssign(result, Expression.PostDecrementAssign(value)), // If false, exit from loop and go to a label. Expression.Break(label, result) ), // Label to jump to. label ) ); // Compile an expression tree and return a delegate. return Expression.Lambda<Func<int, int>>(block, value).Compile(); } static int CSharpFact(int value) { int result = 1; while (value > 1) { result *= value--; } return result; } 

Here are three runs made on the i7-920. Build - Release x64

 583 542 660 577 578 666 550 558 652 576 575 648 570 574 641 560 554 640 558 551 650 561 551 666 624 638 683 564 581 647 -3778851060... 482 482 557 489 490 580 514 517 606 541 537 626 551 524 641 563 555 631 552 558 644 572 541 652 591 549 652 562 552 639 -3778851060... 482 482 560 507 503 591 525 543 596 555 531 609 553 556 634 540 552 640 579 598 635 607 554 639 588 585 679 547 560 643 -3778851060... 

Medium: 554,549,634

Static versus IL - IL 1% faster (!) I don’t know why, although

Static vs ET - static 14% faster than expression tree


EDIT (February 2014) . I just ran the code above (with very slight changes) on .NET 4.5 and faster than the CPU and got new result sets: Method / ET - 9%, method / IL - 4%

Therefore, the previous results are no longer valid - calling a static method is always faster .

* Not sure if this is new hardware (i7-3820) or new .NET, or maybe I did something wrong in the old test. *

Another interesting result is that in 32-bit the same code shows absolutely NO difference between 3.

 Method IL ET -------------------- 368 382 399 367 382 399 367 382 399 367 382 400 367 383 400 367 382 399 367 383 399 367 382 399 367 382 399 367 383 400 367 382 399 367 382 399 367 382 399 367 382 399 367 383 400 367 382 400 367 383 399 367 383 400 367 382 399 367 382 400 -7557702120... -------------------- 367.05 382.30 399.35 
+9
source

You must create a Compile() tree of expressions.

+4
source

Here is an example of a dynamic calculator that uses compiled code. Source is available.

http://www.c-sharpcorner.com/UploadFile/mgold/CodeDomCalculator08082005003253AM/CodeDomCalculator.aspx

+1
source

It depends on usage and optimization. The benchmark may lie if your tests are not perfect. You must know the rules to do it right.

First rules

  • The static method can be optimized at compile time and can be built-in.
  • The method of emitting IL (DynamicMethod) can be faster in terms of pure IL, because you can optimize as you want (if you can do it better than the standard optimizer)
  • The expression tree is based on DynamicMethod, but you cannot optimize it manually.

Second rule

  • Performance is represented by a call engine and pure method execution.
  • delegate calling method means overhead
  • built-in call cancellation mechanism.
  • DynamicMethod can only be embedded in another DynamicMethod
  • DynamicMethod and the expression tree are most often called using a delegate.
  • Delegating an instance method is faster than delegating to a static method.

remember:

  • The static method is often faster for small body methods.
  • DynamicMethod may be faster if the invocation mechanism is not an error.
  • The expression tree cannot be faster than DynamicMethod, but sometimes (rarely) can be faster than the static method, depending on how you express it.

conclusion:

performance depends on context. keep using the static method if possible. Performance may change when the optimizer changes.

-one
source

Source: https://habr.com/ru/post/1492866/


All Articles