Today I wanted to explore and benchmark a C# feature that I've never used before, the dynamic type.
A simple way to group programming languages is based on their type system. A statically typed language will enforce correct usage of types. Usually a compiler will check a types usage, and if you call a method that doesn't exist on a type or some other non allowable way, you'll get a compiler error. Think languages like C++, Java or C#.
We usually like to think of programming languages as being either dynamic or statically typed, and most languages push you towards one or the other. C# and Python are examples of languages that have the ability to do both. C# has a dynamic keyword that eliminates compile time checks and Python has recently introduced static type checking via mypy.
There are pros and cons to both type systems, I personally favor static typing. But C# gives you the freedom to use dynamic typing.
In my 5 years of professional programming I haven't run into a case yet where I have needed to use the dynamic keyword. But sometimes using the dynamic keyword and bypassing the compiler is really the only feasible option. But they do exist, and to quote the Microsoft docs:
Therefore, you do not have to be concerned about whether the object gets its value from a COM API, from a dynamic language such as IronPython, from the HTML Document Object Model (DOM), from reflection, or from somewhere else in the program. ~ Microsoft Docs
So if you happen to need it, how would you use it and how does it perform?
Using the dynamic keyword is super easy. Any time you would use a regular type or a var, just use dynamic instead.
Then you can continue to use your dynamic type in place of any other regular type.
The compiler will never throw an error on a dynamic type, but if you aren't careful your program might blow up at runtime if you're using it incorrectly.
Lets try to get a glimpse at the performance characteristics of the dynamic keyword. I'm using the ever useful library BenchmarkDotNet. I've created a couple of examples to compare the performance of dynamic via static typed examples.
I'm summing an int, concatenating a string, then calling a method on both a local object then a local struct. To view and run the benchmark yourself please visit the github repo.
Running these benchmark gives us the following numbers:
The two most useful columns are the Mean and Allocated column. The Mean column indicates how long a benchmark took to run. The Allocated column indicates how much heap memory was allocated for a particular benchmark.
You can see that the dynamic performance was overall worse than actually using types.
For summing of numbers, dynamic was ~18x slower and allocated memory.
The string concat was roughly the same performance, which was a little surprising to me.
Then for the user benchmarks, it was easy to tell that dynamic is not the way to go.
So overall, using dynamic is not very performant, but what about it makes it slower?
One way we could find out would be to investigate what code is actually compiled. Some features, like the dynamic keyword, extension methods, are natively supported in IL (Intermediate Language), which is what C# gets compiled down into. Before C# is transformed into IL code, it might be first be transformed into some other C# code. What transformations occur for dynamic keywords?
I took the liberty of installing dnSpy. dnSpy is a debugger and .NET assembly editor. It gives us a picture into what the compiler does with our source code.
If you download dnSpy, select the dll of this benchmarking library, it will convert the compiled benchmark back into C# for us to view. So what does it look like?
Dang, that's a lot of extra code the compiler had to generate. The convenience of using the dynamic keyword comes at the cost of all that code.
The SumOneTo100() method is basically the same as we originally wrote it. Which means what we wrote can be easily transformed into IL. But the DynamicSumOneTo100() is a freakin' mess. A lot of transformations had to occur before converting into IL.
I've never seen code like this before. Guessing from the example it seems to be dynamically adding the method that is needed right before it is called.
Our string concat method has even more changes to it, but ironically those changes didn't seem to impact performance as much.
Maybe there's just a one time setup cost of using dynamic types? Or summing numbers is a cheaper operation than concating strings, so it would make sense if there was a larger difference when summing numbers versus concating strings.
I reran the summing numbers benchmark with different values on N. Once when N = 100 (like last time), then also with N = 10,000. If using the dynamic type is just a one time setup, I would imagine that for the N = 10,000 the dynamic and non-dynamic version would be closer in performance.
So after running that benchmark, it turns out my one time setup cost theory was incorrect. The bad performance was roughly the same for a bigger value of N.
I don't really know why the performance of the string benchmark was a lot closer. If anyone out there can help explain why our string benchmark was roughly equivalent while our summing benchmark was drastically different I was love to learn.
This might not be a feature you've ever used before, but it's interesting to know that it's there and what it's basic performance is like.
Overall, if you have a choice to use regular types or dynamic, always go with regular types. The compiler will check your code and then give you better performance.
If you run into a reason to use dynamic, by all means use it, but know that there are tradeoffs for the convenience of not having the compiler check your types.
I’m Morgan Kenyon. I’m a .NET developer working in the DFW area. I love solving a good problem and want to talking about the tech I use. If you found this article helpful or thought provoking leave a comment and lets connect over LinkedIn!