It is undeniable that most of us use enums. And most people have a love/hate relationship with them. You can say enums are as fast as they possibly can be, but isn’t it just a biased statement? Or can they get any faster? We will check if it is true in this blog article.
The “test” case
Now that we have our statement to check, we want to test the enums, and thus we need a good test case. We’re going to test the speed of typical operations on a large enum and on a regular one. The large enum is going to be the one holding a list of all the countries. It can be found here.
As for the small enum, it is going to be a short subset of all the countries. In my case it looks like this:
We are still missing a critical component, though, and to determine whether a strategy is fast, we must have something to compare it to. This is exactly what we are going to do now - the alternative for using enums in strander way will be to use a Git repository created just for that purpose.
The Enum Generators
The git repository I’m referring to is the one by Andrew Lock and can be found here. Please install it as a NugetPackage in your project.
In my case the project is a console application, and my csproj looks like this:
As you can see, I have two packages installed. One of them is the aforementioned Enum Generators and the second is BenchmarkDotNet - an excellent solution for benchmarking your application - the one I use myself and I strongly advise you to do the same.
Let’s get back to Enums Generators. As you can see on the readme it is a Source Generator package that generates extension methods for enums to allow fast "reflection". That is exactly what it does, and I can guarantee you that it does it well.
The base code
First, we need to take a look at the enums we will be using. There are two of them. The big one...
… and the small one.
They are both standard enums : one with around 250 elements and one with 4. The one interesting thing about them is the EnumExtensions argument - this argument is what makes them unique. It is provided by the previously mentioned Git repository. Before we look at how it works under the hood, let's have a look at the performance boost we're after.
We're using the benchmark approach to gauge our progress. I'll begin by benchmarking the small enum. To accomplish this, I created the class FewCountriesBenchmark .
As you can see, we're comparing three commonly used methods: ToString , IsDefined , and TryParse . Each of these methods is called by the built-in enum as well as the extension package.
A similar class was created for the large enum. It was named CountriesBenchmark . A shocker, I know.
We need to change the configuration to release and run the application with Programs.cs looking like this in order to run the benchmarking process:
After a few minutes we will be able to see the results and I must say they are very interesting. First lets look at the results for the smaller enum. As it was to be expected the extended version is much faster plus is doesn’t allocate any extra memory. It is so fast that the benchmarking IsDefined even gave us a warning: “FewCountriesBenchmark.FastIsDefined: Default -> The method duration is indistinguishable from the empty method duration” . Comparing to the out-of-the-box IsDefined, that also allocates memory it is a huge performance boost. The ToString and TryParse are also much faster.
Now we'll go to the very big enum. The outcomes in this example are quite fascinating - in the instance of the first two methods, ToString and IsDefined , the performance of extension procedures is considerably better than that of out-of-the-box methods, both in terms of speed and memory. TryParse , on the other hand, is a different story, as the extended version is a little slower. Memory-wise, it's the same story.
Under the hood
We know that in most cases, the solution presented is much faster. I hope most of you can see the benefit of using this programme. But how exactly does it work? That is an excellent question. The magic begins when we add an Attribute.
The magic is contained in the 2507 usages - the Attribute generated all of them when we added it. Expand the usages and pick one - no matter where you click, you will most likely end up with an auto-generated file.
This file contains all of the new methods, and it is the rewriting of the methods that provides the extra performance, so study it carefully and see what changes have been made. You can even consider it your homework.
We haincreased the speed of enums. Well, it was not exactly us who did it, but we did once utilise NuGet magic. Now, let's get serious. We used a scientific approach to evaluate our code once more. It was all about enums this time, and we observed how broadening the implementation can result in big gains. I hope that helps you and that we can all utilise it in our daily job.
If you have any questions, please drop me a line at firstname.lastname@example.org.
Till next time. Keep coding.