.NET Forum / Languages / C# / April 2008
Delegates vs. MethodInfo When Calling Code Dynamically
|
|
Thread rating:  |
Tom Corcoran - 09 Nov 2007 18:48 GMT I've been led to believe by several articles, particularly Eric Gunnerson's C# Calling Code Dynamically, that calling a method dynamically through Reflection was much slower than through a Delegate. My testing showed that actually it was six times faster: 0.5 seconds for 100,000 iterations versus 3.1 seconds.
Can anyone explain why? Something in the way I coded it? I'd appreciate any insights.
Here's the code (in a Windows Form with two buttons). Operating system is Windows XP Pro. Visual Studio 2005. .Net Framework 2.0.
const string OP_GREATER_THAN_OR_EQUAL = "op_GreaterThanOrEqual"; const string OP_LESS_THAN_OR_EQUAL = "op_LessThanOrEqual"; const decimal TEST_VAL = 100000.00M; const decimal BENCHMARK_VAL = 101000.00M; const int TEST_LIMIT = 100000;
private delegate bool OperatorTest( decimal pTestVal, decimal pBenchmarkVal );
private void btnMethodInfo_Click( object sender, EventArgs e ){ DateTime start = DateTime.Now; for( int i = 0; i < TEST_LIMIT; i++ ) { Type t1 = TEST_VAL.GetType(); Type t2 = BENCHMARK_VAL.GetType(); MethodInfo mi = typeof( decimal ).GetMethod( OP_GREATER_THAN_OR_EQUAL, new Type[] { t1, t2 } ); bool isValid = ( bool )mi.Invoke( null, new object[] { TEST_VAL, BENCHMARK_VAL } ); } DateTime stop = DateTime.Now; Console.WriteLine( "Elapsed time by MethodInfo = " + ( stop- start ) ); }
private void btnDelegate_Click( object sender, EventArgs e ){ DateTime start = DateTime.Now; for( int i = 0; i < TEST_LIMIT; i++ ) { Delegate d = Delegate.CreateDelegate( typeof( OperatorTest ), typeof( decimal ), OP_GREATER_THAN_OR_EQUAL ); bool isValid = ( bool )d.DynamicInvoke( new object[] { TEST_VAL, BENCHMARK_VAL } ); } DateTime stop = DateTime.Now; Console.WriteLine( "Elapsed time by Delegate = " + ( stop - start ) ); }
 Signature Tom Corcoran
Peter Bromberg [C# MVP] - 09 Nov 2007 21:52 GMT I think you need to go back and revisit your code. Your delegate is being created multiple times inside the loop, and this is eating up CPU cycles. Only need to create the delegate one time. Haven't looked further, but there could be other gotchas too. -- Peter http://www.eggheadcafe.com unBlog: http://petesbloggerama.blogspot.com BlogMetaFinder: http://www.blogmetafinder.com
> I've been led to believe by several articles, particularly Eric Gunnerson's > C# Calling Code Dynamically, that calling a method dynamically through [quoted text clipped - 43 lines] > Console.WriteLine( "Elapsed time by Delegate = " + ( stop - start ) ); > } Nicholas Paldino [.NET/C# MVP] - 10 Nov 2007 15:21 GMT I see a few other things going on here.
First, you (the OP) should be using the Stopwatch class in the System.Diagnostics namespace to perform the timing. It will provide a much more accurate result than the DateTime class.
Regarding the loop that calls through reflection, every iteration through the loop is getting the decimal type three times, and then getting the method, and then calling the method. You want to test just calling the method, not all those other things. To that end, the code in that event handler should look like this:
private void btnMethodInfo_Click( object sender, EventArgs e ) { // The stopwatch instance. Stopwatch sw = new Stopwatch();
// Get the type of decimal. Type decimalType = typeof(decimal);
// Get the method info. MethodInfo mi = decimalType.GetMethod(OP_GREATER_THAN_OR_EQUAL, new Type[] { decimalType, decimalType });
// NOW start the timer. sw.Start();
// Iterate. for( int i = 0; i < TEST_LIMIT; i++ ) { // Invoke the method. bool isValid = (bool) mi.Invoke(null, new object[] { TEST_VAL, BENCHMARK_VAL } ); }
// Stop the timer. sw.Stop();
// Write the result. Console.WriteLine( "Elapsed time by MethodInfo = " + sw.Elapsed ); }
Regarding the loop that calls through the delegate, most of what applies in the other method applies here. You only need to create the delegate once, the types once, etc, etc:
private void btnDelegate_Click( object sender, EventArgs e ) { // The stopwatch instance. Stopwatch sw = new Stopwatch();
// Get the delegate. Delegate d = Delegate.CreateDelegate(typeof(OperatorTest), typeof(decimal), OP_GREATER_THAN_OR_EQUAL);
// NOW start the timer. sw.Start();
// Iterate. for( int i = 0; i < TEST_LIMIT; i++ ) { // Invoke the delegate. bool isValid = (bool) d.DynamicInvoke(new object[] { TEST_VAL, BENCHMARK_VAL } ); }
// Stop the stopwatch. sw.Stop();
// Output the result. Console.WriteLine("Elapsed time by Delegate = " + sw.Elapsed); }
I think that doing it this way will produce very different (and expected) results for the OP.
 Signature - Nicholas Paldino [.NET/C# MVP] - mvp@spam.guard.caspershouse.com
>I think you need to go back and revisit your code. Your delegate is being > created multiple times inside the loop, and this is eating up CPU cycles. [quoted text clipped - 60 lines] >> Console.WriteLine( "Elapsed time by Delegate = " + ( stop - start ) ); >> } Tom Corcoran - 12 Nov 2007 13:50 GMT Nicholas and Peter, thank you both very much. Nicholas, thank you in particular for taking the time to code a better version and for teaching me about Stopwatch. Using your code and Stopwatch, the elapsed time of the Delegates method was still about 3.5 times SLOWER than that of the MethodInfo method. This seems counter to all that I have heard about Reflection and Delegates. I'd appreciate any other ideas you might have, as I'm putting a change into an Xml web service that will likely get a high traffic needing a dynamically called comparison operator.
 Signature Tom Corcoran
> I see a few other things going on here. > [quoted text clipped - 136 lines] > >> Console.WriteLine( "Elapsed time by Delegate = " + ( stop - start ) ); > >> } Jon Skeet [C# MVP] - 12 Nov 2007 13:57 GMT On Nov 12, 1:50 pm, Tom Corcoran <TomCorco...@discussions.microsoft.com> wrote:
> Nicholas and Peter, thank you both very much. Nicholas, thank you in > particular for taking the time to code a better version and for teaching me [quoted text clipped - 4 lines] > change into an Xml web service that will likely get a high traffic needing a > dynamically called comparison operator. Could you show your new complete code? It would be useful to have a copy that we can run ourselves. Was your timing performed within the debugger or not?
Jon
Tom Corcoran - 12 Nov 2007 14:11 GMT Jon,
Thanks for your reply. Here is the new code as rewritten by Nicholas. The timing was performed within the debugger.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Reflection; using System.Text; using System.Windows.Forms;
namespace DelegatesProblem { public partial class Form1 : Form { const string OP_GREATER_THAN_OR_EQUAL = "op_GreaterThanOrEqual"; const string OP_LESS_THAN_OR_EQUAL = "op_LessThanOrEqual"; const decimal TEST_VAL = 100000.00M; const decimal BENCHMARK_VAL = 101000.00M; const int TEST_LIMIT = 100000;
private delegate bool OperatorTest( decimal pTestVal, decimal pBenchmarkVal );
public Form1() { InitializeComponent(); }
private void btnMethodInfo_Click( object sender, EventArgs e ) { this.Cursor = Cursors.WaitCursor; Stopwatch sw = new Stopwatch(); Type decimalType = typeof( decimal ); MethodInfo mi = decimalType.GetMethod(OP_GREATER_THAN_OR_EQUAL, new Type[]{decimalType, decimalType} ); sw.Start(); for( int i = 0; i < TEST_LIMIT; i++ ) { bool isValid = ( bool )mi.Invoke( null, new object[] { TEST_VAL, BENCHMARK_VAL } ); } sw.Stop(); Console.WriteLine( "Elapsed time by MethodInfo = " + sw.Elapsed ); this.Cursor = Cursors.Default; }
private void btnDelegate_Click( object sender, EventArgs e ) { this.Cursor = Cursors.WaitCursor; Stopwatch sw = new Stopwatch(); Delegate d = Delegate.CreateDelegate( typeof( OperatorTest ), typeof( decimal ), OP_GREATER_THAN_OR_EQUAL ); sw.Start(); for( int i = 0; i < TEST_LIMIT; i++ ) { bool isValid = ( bool )d.DynamicInvoke( new object[] { TEST_VAL, BENCHMARK_VAL } ); } sw.Stop(); Console.WriteLine( "Elapsed time by Delegate = " + sw.Elapsed ); this.Cursor = Cursors.Default; }
private void btnDone_Click( object sender, EventArgs e ) { this.Close(); this.Dispose(); } } }
 Signature Tom Corcoran
> On Nov 12, 1:50 pm, Tom Corcoran > <TomCorco...@discussions.microsoft.com> wrote: [quoted text clipped - 12 lines] > > Jon Jon Skeet [C# MVP] - 12 Nov 2007 14:32 GMT On Nov 12, 2:11 pm, Tom Corcoran <TomCorco...@discussions.microsoft.com> wrote:
> Thanks for your reply. Here is the new code as rewritten by Nicholas. The > timing was performed within the debugger. Four things:
1) Code within a Windows Form (or any MarshalByRefComponent) has a few optimisations disabled. 2) It's easier to compile and run short console apps :) 3) Performance testing in a debugger is a really bad idea 4) Calling DynamicInvoke isn't the same as directly calling the delegate in a strongly-typed manner
Here's the equivalent code, rewritten as a console app, including a direct invocation, and with more iterations to give more useful results:
using System; using System.Diagnostics; using System.Reflection;
class Benchmark { const string OpGreaterThanOrEqual = "op_GreaterThanOrEqual";
const int Iterations = 1000000; const decimal FirstValue = 100000.00m; const decimal SecondValue = 101000.00m;
private delegate bool OperatorTest (decimal first, decimal second);
static void Main() { RunDelegateDynamic(); RunDelegateDirect(); RunReflection(); }
static void RunDelegateDirect() { OperatorTest d = (OperatorTest) Delegate.CreateDelegate (typeof(OperatorTest), typeof(decimal), OpGreaterThanOrEqual);
Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { d(FirstValue, SecondValue); } sw.Stop(); Console.WriteLine ("Direct invoke: "+sw.ElapsedMilliseconds); }
static void RunDelegateDynamic() { Delegate d = Delegate.CreateDelegate (typeof(OperatorTest), typeof(decimal), OpGreaterThanOrEqual); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { d.DynamicInvoke(new object[] { FirstValue, SecondValue }); } sw.Stop(); Console.WriteLine ("Dynamic invoke: "+sw.ElapsedMilliseconds); }
static void RunReflection() { MethodInfo mi = typeof(decimal).GetMethod(OpGreaterThanOrEqual, new Type[] {typeof(decimal), typeof(decimal) }); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { mi.Invoke(null, new object[] { FirstValue, SecondValue }); } sw.Stop(); Console.WriteLine ("Reflection: "+sw.ElapsedMilliseconds); } }
Here are the results on my box, not running Dynamic invoke: 5433 Direct invoke: 82 Reflection: 2048
So yes, calling DynamicInvoke on a delegate is still slower than using reflection - I suspect it's using reflection under the hood, but with some extra hoops. However, calling the delegate *directly* (which is what you should be aiming to do normally) is much, much faster than reflection.
Jon
Tom Corcoran - 12 Nov 2007 16:22 GMT Jon,
I got comparable results. Thank you for showing me these things. I know a bit more about Delegates now and much more about running benchmark tests. I appreciate your time on it.
Sincerely, Tom Corcoran
> On Nov 12, 2:11 pm, Tom Corcoran > <TomCorco...@discussions.microsoft.com> wrote: [quoted text clipped - 92 lines] > > Jon Jon Skeet [C# MVP] - 12 Nov 2007 19:39 GMT > I got comparable results. Thank you for showing me these things. I know a > bit more about Delegates now and much more about running benchmark tests. I > appreciate your time on it. No problem. I'm a big fan of small console apps, because you basically don't need any more code than what you're actually trying to run - no messing around with buttons, events and the like :)
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Creativ - 13 Nov 2007 20:08 GMT I've seen three ways. MethodInfo and strong type delegate make sense. In which situation do you need dynamicInvoke
Jon Skeet [C# MVP] - 13 Nov 2007 20:15 GMT > I've seen three ways. MethodInfo and strong type delegate make sense. > In which situation do you need dynamicInvoke When you don't know the type of the delegate at compile-time.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Creativ - 15 Nov 2007 08:00 GMT > > I've seen three ways. MethodInfo and strong type delegate make sense. > > In which situation do you need dynamicInvoke [quoted text clipped - 4 lines] > Jon Skeet - <sk...@pobox.com>http://www.pobox.com/~skeet Blog:http://www.msmvps.com/jon.skeet > If replying to the group, please do not mail me too Can you give me an example? I don't see it. I thought MethodInfo( reflection) will just do it and faster.
Marc Gravell - 15 Nov 2007 08:33 GMT But MethodInfo is a different beast to a Delegate.
I'm not sure it is a good example (I would use typed On{blah} method myself), but one /potential/ candidate for this would be a general-purpose event invoke from EventHandlerList...
protected void OnEvent(object key, object sender, EventArgs args) { Delegate handler = Events[key]; if (handler != null) { handler.DynamicInvoke(sender, args); } }
Other use-cases might include scenarios such as implementing ISynchronizeInvoke:
public interface ISynchronizeInvoke { bool InvokeRequired { get; } IAsyncResult BeginInvoke(Delegate method, object[] args); object EndInvoke(IAsyncResult result); object Invoke(Delegate method, object[] args); }
I'm not saying that they are common to write, but certainly the latter gets /used/ very often (think System.Windows.Forms.Control)
Marc
Jon Skeet [C# MVP] - 15 Nov 2007 08:34 GMT > > > I've seen three ways. MethodInfo and strong type delegate make sense. > > > In which situation do you need dynamicInvoke [quoted text clipped - 3 lines] > Can you give me an example? I don't see it. I thought > MethodInfo( reflection) will just do it and faster. Consider trying to implement ISynchronizeInvoke.Invoke - you're presented with a Delegate, and an object[]. How are you going to execute the delegate? I suppose you *could* get the invocation list and execute it that way, but I suspect that's basically what DynamicInvoke does under the hood anyway.
Jon
Valeriu Lacatusu - 13 Apr 2008 04:38 GMT Hello I've changed some of the values used in your benchmark test(i.e: the delegate type and some of the constants) to match a more realistic situation(upon my opinion...) for the type of function we are trying to execute using invocation and i've obtained different results regarding the difference between the direct invocation and the other 2 types.
Dynamic invoke: 12976 Direct invoke: 12505 Reflection: 13332
Could someone confirm my results and suggest some reason for that?
using System; using System.Diagnostics; using System.Reflection;
class Benchmark { const string OpGreaterThanOrEqual = "op_GreaterThanOrEqual";
const int Iterations = 100000;//changed const int V1 = 100000; const int V2 = 100000; const decimal FirstValue = 100000.00m; const decimal SecondValue = 101000.00m;
private delegate bool OperatorTest(decimal first, decimal second); private delegate void Blah(int first, int second);//added
static void Main() { RunDelegateDynamic(); RunDelegateDirect(); RunReflection(); Console.ReadLine(); }
//Some more time consuming function public static void BlahTst(int first, int second) { int res = 0; for (int i = 0; i < 100000; i++) { if (first < second) { res = -1; } else { res = 1; } } } static void RunDelegateDirect() { Blah d = (Blah)Delegate.CreateDelegate(typeof(Blah), (Type)typeof(Benchmark), "BlahTst");
Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { d(V1, V2); } sw.Stop(); Console.WriteLine("Direct invoke: " + sw.ElapsedMilliseconds); /* OperatorTest d = (OperatorTest)Delegate.CreateDelegate (typeof(OperatorTest), typeof(decimal), OpGreaterThanOrEqual);
Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { d(FirstValue, SecondValue); } sw.Stop(); Console.WriteLine("Direct invoke: " + sw.ElapsedMilliseconds); */ }
static void RunDelegateDynamic() { Delegate d = Delegate.CreateDelegate(typeof(Blah), (Type)typeof(Benchmark), "BlahTst"); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { d.DynamicInvoke(new object[] { V1, V2 }); } sw.Stop(); Console.WriteLine("Dynamic invoke: " + sw.ElapsedMilliseconds);
/* Delegate d = Delegate.CreateDelegate(typeof(OperatorTest), typeof(decimal), OpGreaterThanOrEqual); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { d.DynamicInvoke(new object[] { FirstValue, SecondValue }); } sw.Stop(); Console.WriteLine("Dynamic invoke: " + sw.ElapsedMilliseconds); */ }
static void RunReflection() { MethodInfo mi = typeof(Benchmark).GetMethod("BlahTst", new Type[] { typeof(int), typeof(int) }); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { mi.Invoke(null, new object[] { V1, V2 }); } sw.Stop(); Console.WriteLine("Reflection: " + sw.ElapsedMilliseconds);
/* MethodInfo mi = typeof(decimal).GetMethod(OpGreaterThanOrEqual, new Type[] { typeof(decimal), typeof(decimal) }); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { mi.Invoke(null, new object[] { FirstValue, SecondValue }); } sw.Stop(); Console.WriteLine("Reflection: " + sw.ElapsedMilliseconds); */ } }
Jon Skeet [C# MVP] - 13 Apr 2008 15:35 GMT > Hello > I've changed some of the values used in your benchmark test(i.e: the delegate type and some of the constants) to match a more realistic situation(upon my opinion...) for the type of function we are trying to execute using invocation and i've obtained different results regarding the difference between the direct invocation and the other 2 types. [quoted text clipped - 4 lines] > > Could someone confirm my results and suggest some reason for that? Yes - the bulk of the time is now spent within the BlahTst method itself. Calling the method dynamically doesn't make the method itself run any slower - it just means the time taken to transfer execution from the calling method to BlahTst is slower. When that becomes less significant (because BlahTst is doing more work) you'll see less difference in the results - as your numbers demonstrate.
Jon
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|