What is the best way to create dynamic method on a fly but have it same efficient if it was compiled in VS?
Say I want create a calculator. User inputs formula say A + B / C * 0.5;
What I want is to be able to create something like Func which will accept A,B,C as double parameters and return double.
The parameters type and the return type are always double. Number of parameters are variable but at least one.
These formulas can be changed / added often. Once a formula 'compiled' it will be part of low latency code which can be called 1000 times / sec.
I need to find simple and reliable way to build it but it must have exact performance qualities of statically built and optimised method.
I have found Microsoft blog on this (Generating Dynamic Methods ) and compared performance between static method, 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 3 runs made on 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...
Averages: 554 549 634
Static vs IL - IL 1% faster (!) no idea why though
Static vs ET - static 14% faster than expression tree
EDIT (Feb 2014) : I just ran the code above (with very slight modifications) on .NET 4.5 and faster CPU and got the new sets of results: Method / ET - 9%, Method / IL - 4%
Hence the previous results are not valid any more - the static method call is always faster..
*Not sure whether it is new hardware (i7-3820) or new .NET or perhaps I did something wrong in the old test.*
Another interesting result is that in 32-bit the very same code shows absolutely NO difference between the 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With