Home » Software Dev

Static vs. Dynamic vs. Reflected C#

31. August 2009 by Martin Rue 0 Comments

python C# is a statically typed language. This means that types are checked at compile time rather than at runtime. However, in C# 4.0, the dynamic keyword will be introduced. That’s right, we will be statically typing something to be a dynamic type. The dynamic keyword essentially tells the C# compiler to defer type resolution until runtime. At runtime, the DLR (Dynamic Language Runtime) will take over and attempt to resolve any dynamic references for us – or throw the appropriate exceptions. Before dynamic, we’ve been used to reflection – a method of inspecting types and doing various things to them. Many of us have used reflection to achieve more dynamic things with our code.

A recent post from Phil Haack shows a cool example of creating a dynamic dictionary. While the post was esoteric in nature, it got me thinking about how people might use the dynamic type in the future. Arguably, having to create class definitions could be considered unnecessary plumbing work in certain situations, such as when you’re working with JSON data and you *just* want to evaluate the data and get an object back. Sometimes I want to simply ignore the concept of a type and return a dynamic object, in the same way you might use a tuple in a functional language, or a variable in JavaScript.

As Phil demonstrates, the inclusion of the dynamic type in C# 4.0 allows us to build these kinds of data structures quite easily. But what about performance? Of course, anyone who’s used reflection for more than 5 minutes knows how dangerous it can be in terms of performance – but what about dynamic types? They’re being resolved at runtime, right? There’s got to be some overhead. Well, I decided to find out.

The example calls a method a varying number of times using different dispatch methods (static, dynamic & reflected) and measures how long the process takes. The results are shown below:

10 Static Invocations: 0 ms

100 Static Invocations: 0 ms

1,000 Static Invocations: 0 ms

10,000 Static Invocations: 0 ms

100,000 Static Invocations: 1 ms

1,000,000 Static Invocations: 11 ms

10 Dynamic Invocations: 67 ms

100 Dynamic Invocations: 0 ms

1,000 Dynamic Invocations: 0 ms

10,000 Dynamic Invocations: 2 ms

100,000 Dynamic Invocations: 25 ms

1,000,000 Dynamic Invocations: 255 ms

10 Reflected Invocations: 0 ms

100 Reflected Invocations: 2 ms

1,000 Reflected Invocations: 22 ms

10,000 Reflected Invocations: 164 ms

100,000 Reflected Invocations: 1659 ms

1,000,000 Reflected Invocations: 15877 ms

As expected, with a small number of invocations we don’t see much change with the static calls. There’s a clear progression as the number of invocations rises, which is not surprising. The dynamic invocations are interesting however. 67 miliseconds for 10 calls? Are you kidding me? This is worse than reflection… hold on a second, 0 miliseconds for 100? and 1000? What’s going on?

If you were confused for a second, you weren’t alone. Some background details regarding how dynamic dispatch actually works is necessary to understand these results. Unlike static dispatch, the address of a dynamic call is not determined at compile time (the reason why the static tests mostly took 0 ms until we got into the big numbers). A dynamic call’s ultimate destination is determined at runtime, and therefore must be located as a matter of urgency when the call is first made. Understandably then, the first call has a certain amount of overhead while the DLR resolves it. From the data above, you can see that initially we spent 67 ms resolving and making the first dynamic call. However, in subsequent invocations, the location of the dynamic call was known and involved only the overhead of looking up the previously calculated destination.

Reflection. [Insert disappointed teacher-to-student look here]. Did you honestly expect anything else? As your natural instinct may have already told you, reflection for this kind of thing is blatantly inefficient. Of course, reflection is not intended for these purposes in the first place. Why would you ever invoke a method 1 million times using reflection unless you were suffering from some kind of attention-deficit/hyperactivity disorder. In the smaller tests, the overhead is noticed much less, but when we approach the 10,000 invocations mark, the overhead is becoming greatly emphasised.

Admittedly, my investigation here could be extended to take into consideration a more realistic application. If anyone would like to extend the test, feel free to enhance the code below and leave a comment detailing your findings.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Diagnostics;
   6: using System.Reflection;
   7:  
   8: namespace ConsoleApplication
   9: {
  10:     public class Echoer
  11:     {
  12:         public string Echo(string message)
  13:         {
  14:             return message;
  15:         }
  16:     }
  17:  
  18:     class Program
  19:     {
  20:         static void Main(string[] args)
  21:         {
  22:             Console.WriteLine("10 Static Invocations: {0} ms", staticTest(10));
  23:             Console.WriteLine("10 Dynamic Invocations: {0} ms", dynamicTest(10));
  24:             Console.WriteLine("10 Reflected Invocations: {0} ms\n", reflectedTest(10));
  25:  
  26:             Console.WriteLine("100 Static Invocations: {0} ms", staticTest(100));
  27:             Console.WriteLine("100 Dynamic Invocations: {0} ms", dynamicTest(100));
  28:             Console.WriteLine("100 Reflected Invocations: {0} ms\n", reflectedTest(100));
  29:  
  30:             Console.WriteLine("1,000 Static Invocations: {0} ms", staticTest(1000));
  31:             Console.WriteLine("1,000 Dynamic Invocations: {0} ms", dynamicTest(1000));
  32:             Console.WriteLine("1,000 Reflected Invocations: {0} ms\n", reflectedTest(1000));
  33:  
  34:             Console.WriteLine("10,000 Static Invocations: {0} ms", staticTest(10000));
  35:             Console.WriteLine("10,000 Dynamic Invocations: {0} ms", dynamicTest(10000));
  36:             Console.WriteLine("10,000 Reflected Invocations: {0} ms\n", reflectedTest(10000));
  37:  
  38:             Console.WriteLine("100,000 Static Invocations: {0} ms", staticTest(100000));
  39:             Console.WriteLine("100,000 Dynamic Invocations: {0} ms", dynamicTest(100000));
  40:             Console.WriteLine("100,000 Reflected Invocations: {0} ms\n", reflectedTest(100000));
  41:  
  42:             Console.WriteLine("1,000,000 Static Invocations: {0} ms", staticTest(1000000));
  43:             Console.WriteLine("1,000,000 Dynamic Invocations: {0} ms", dynamicTest(1000000));
  44:             Console.WriteLine("1,000,000 Reflected Invocations: {0} ms\n", reflectedTest(1000000));
  45:             
  46:             Console.ReadKey();
  47:         }
  48:  
  49:         static long staticTest(int times)
  50:         {
  51:             Echoer staticEchoer = new Echoer();
  52:             Stopwatch stopwatch = new Stopwatch();
  53:             stopwatch.Start();
  54:  
  55:             for (int i = 0; i < times; i++)
  56:             {
  57:                 staticEchoer.Echo("hello world");
  58:             }
  59:  
  60:             stopwatch.Stop();
  61:             return stopwatch.ElapsedMilliseconds;
  62:         }
  63:  
  64:         static long dynamicTest(int times)
  65:         {
  66:             dynamic dynamicEchoer = new Echoer();
  67:             Stopwatch stopwatch = new Stopwatch();
  68:             stopwatch.Start();
  69:  
  70:             for (int i = 0; i < times; i++)
  71:             {
  72:                 dynamicEchoer.Echo("hello world");
  73:             }
  74:  
  75:             stopwatch.Stop();
  76:             return stopwatch.ElapsedMilliseconds;
  77:         }
  78:  
  79:         static long reflectedTest(int times)
  80:         {
  81:             object reflectedEchoer = new Echoer();
  82:             Type reflectedEchoerType = reflectedEchoer.GetType();
  83:             Stopwatch stopwatch = new Stopwatch();
  84:             stopwatch.Start();
  85:  
  86:             for (int i = 0; i < times; i++)
  87:             {
  88:                 reflectedEchoerType.InvokeMember("Echo", BindingFlags.InvokeMethod, null, reflectedEchoer, new object[] { "hello world" });
  89:             }
  90:  
  91:             stopwatch.Stop();
  92:             return stopwatch.ElapsedMilliseconds;
  93:         }
  94:     }
  95: }
  96:  

Conclusion

So, what is the eventual conclusion? Well, I set out to discover how efficient the dynamic dispatch feature of the upcoming .NET runtime was, and I’ve been pleasantly surprised to find that it’s not actually that bad at all. With just 255 miliseconds for 1 million calls, it’s certainly not going to be the bottleneck in your code. Knowing the performance factors of using dynamic dispatch makes me more comfortable with considering it as a potential solution to applicable problems.

Comments

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading