C#: The Order of Interfaces Is Important for Casting Performance
Recently there was a discussion in a Pull Request for the .NET runtime on GitHub. In the discussion it was mentioned by Jan Kotas from Microsoft that the order of interfaces is important for the casting performance.
I never heard of that, and looks I’m not alone:
Raise your hand if you knew that. https://t.co/yH9SvREL9H pic.twitter.com/wVAnw3mCxq
— Immo Landwerth (@terrajobst) February 28, 2020
So, let’s see this in action with a little, simple example in .NET Core.
First, let’s define a few interfaces:
public interface IPersonFirst { }
public interface IPerson0 { }
public interface IPerson1 { }
public interface IPerson2 { }
public interface IPerson3 { }
public interface IPerson4 { }
public interface IPerson5 { }
public interface IPerson6 { }
public interface IPerson7 { }
public interface IPerson8 { }
public interface IPerson9 { }
public interface IPerson10 { }
public interface IPersonLast { }
Now let’s create a Person class that implements these interfaces
public class Person : IPersonFirst,
IPerson0,
IPerson1,
IPerson2,
IPerson3,
IPerson4,
IPerson5,
IPerson6,
IPerson7,
IPerson8,
IPerson9,
IPerson10,
IPersonLast
{
}
As you can see in the code snippet above, the Person
class implements all the interfaces.
The IPersonFirst
interface is the first defined interface on the Person
class. That means that casting an object to IPersonFirst
should be faster than casting an object to any other IPerson
interface. Casting to IPersonLast
should have the slowest casting performance.
But why should casting to the first interface be faster?
In the CLR, class definitions have an array of implemented interfaces. That means that if you cast to the last interface defined on a class, the runtime needs to walk through the full array to do the cast. If you cast to the first interface, it doesn’t have to walk through the full array.
Of course, in both cases it is very fast for a few casts. But if you do millions of casts, for example in a server-side application, then it can be important!
Let’s see it in action!
To see this effect in action, I’ve written a simple .NET Core console app.
(Note: You could use also Unit Testing and Benchmarking tools/packages, but let’s keep it here very simple)
Below you see the Program
class of that console app. In the Main
method a person
variable of type object is initialized with a new Person
. Then the RunAction
method is called. A lambda is passed to that method: First with a cast to IPersonFirst
, then another time with a cast to IPersonLast
. As we don’t care about the result of the casts, I just use discards (underscores) as assignment targets.
class Program
{
const int castTimes = 10_000_000;
static void Main(string[] args)
{
Console.WriteLine($"Casting {castTimes} times!");
Console.WriteLine();
object person = new Person();
Console.WriteLine($"IPersonFirst:");
RunAction(() => { _ = (IPersonFirst)person; });
Console.WriteLine($"IPersonLast:");
RunAction(() => { _ = (IPersonLast)person; });
}
private static void RunAction(Action action)
{
var watch = Stopwatch.StartNew();
for (int i = 0; i < castTimes; i++)
{
action();
}
watch.Stop();
Console.WriteLine($"{watch.ElapsedTicks} ticks");
Console.WriteLine($"{watch.ElapsedMilliseconds} ms");
Console.WriteLine();
}
}
In the RunAction
method above you can see that a Stopwatch
is started at the beginning. Then the action, which is in our code the corresponding cast, is executed in a for loop for the number that is specified in the castTimes
constant. After that, the Stopwatch
is stopped, and ticks and milliseconds are written to the console.
As you can see in the code above, the castTimes
constant has the value 10.000.000. The output in the console is this (depending on your processor, you might see different values):
Casting 10000000 times! IPersonFirst: 437084 ticks 43 ms IPersonLast: 1022823 ticks 102 ms
Wow, casting to the IPersonFirst
interface is a lot faster. It looks like it’s in this case more than twice as fast.
Let’s increase the castTimes
variable to 1.000.000.000. This is the output:
Casting 1000000000 times! IPersonFirst: 41143822 ticks 4114 ms IPersonLast: 83499796 ticks 8349 ms
Wow, incredible, again, we see the same, casting is more than twice as fast for the IPersonFirst
interface.
What would be the difference if the Person
class has just two interfaces like below:
public class Person : IPersonFirst, IPersonLast { }
Let’s try it, and let’s cast again 1.000.000.000 times. This is the output:
Casting 1000000000 times! IPersonFirst: 41096526 ticks 4109 ms IPersonLast: 44485942 ticks 4448 ms
Ok, looks with just two interfaces, the difference is not as big, but still, you want to define the most important interface first. Casting to the first interface is still a bit faster.
Now let’s define the Person
class with 22 interfaces like this:
public class Person : IPersonFirst,
IPerson0,
IPerson1,
IPerson2,
IPerson3,
IPerson4,
IPerson5,
IPerson6,
IPerson7,
IPerson8,
IPerson9,
IPerson10,
IPerson11,
IPerson12,
IPerson13,
IPerson14,
IPerson15,
IPerson16,
IPerson17,
IPerson18,
IPerson19,
IPerson20,
IPersonLast
{
}
Let’s run it again with 1.000.000.000 casts, and now this is the output:
Casting 1000000000 times! IPersonFirst: 44831688 ticks 4483 ms IPersonLast: 132791946 ticks 13279 ms
Wow, 4 seconds vs. 13 seconds, that’s three times as fast for the first interface compared to the last interface defined on the Person
class.
What does this mean for you?
Now you’ve learned that there’s a difference in performance. The first interface defined on a class has the best performance when it comes to casting.
If you have an application that processes for example a lot of data, you could reach that number of casts shown in this post really quickly.
So, just put the most important interface first, and order all interfaces that a class implements by importance/performance.
Happy coding,
Thomas
Comments (6)
[…] C#: The Order of Interfaces Is Important for Casting Performance (Thomas Claudius Huber) […]
Why didn’t you use Benchmark.NET to gather the metrics? It’s really not that hard, and in fact it makes the code easier to write.
By the way, if you create the Person object like this:
var person = new Person();
Your results will be very different. The difference between the cast times will go down significantly. Here’s what I got with person typed as an object:
| Method | Mean | Error | StdDev | Ratio | RatioSD |
|———— |———:|———-:|———-:|——:|——–:|
| CastToFirst | 1.489 ns | 0.1100 ns | 0.1029 ns | 1.00 | 0.00 |
| CastTo6 | 3.777 ns | 0.1365 ns | 0.1210 ns | 2.55 | 0.20 |
| CastToLast | 4.054 ns | 0.1619 ns | 0.1435 ns | 2.74 | 0.21 |
In this cast, there is a difference. But when person is typed as a Person…
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
|———— |———-:|———-:|———-:|——-:|——:|——–:|
| CastToFirst | 0.0058 ns | 0.0166 ns | 0.0163 ns | 0.0 ns | ? | ? |
| CastTo6 | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.0 ns | ? | ? |
| CastToLast | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.0 ns | ? | ? |
The time to execute the cast is so fast Benchmark.NET can’t even distinguish the difference. BTW here’s the code I wrote:
public class CastingInterfaces
{
private readonly Person person = new Person();
[Benchmark(Baseline = true)]
public IPersonFirst CastToFirst() => (IPersonFirst)this.person;
[Benchmark]
public IPerson6 CastTo6() => (IPerson6)this.person;
[Benchmark]
public IPersonLast CastToLast() => (IPersonLast)this.person;
}
Hey Jason, thanks for these insights. Yes, I noticed that var p = new Person(); produces a totally different output than object p = new Person();. That’s why I wrote it explicitly like this with an object variable. I think if you have already a variable of type Person, you don’t have a need to cast it anyway. Only need would be to cast to explicitly implemented interfaces.
Why didn’t I use Benchmark.NET: I like it, and yes, it’s not too hard. As I wrote in the post, I thought about using benchmarking tools like Benchmark.NET, but most developers are not so familiar with it. That was the reason I went with a very simple console app that everybody can understand and try out without adding any additional tools/packages. The main idea was: Go to file new project and just paste in the code and try it. I had in mind to do eventually a follow up post in the future with benchmarking tools to get people started. So, your comment comes in a good time. :-)
Thanks for the code, looks good!
Have a great weekend!
Cheers,
Thomas
[…] C#: The Order of Interfaces Is Important for Casting Performance – Thomas Claudius Huber […]
Thanks Thomas, great experiment. For me the key lessons learn from this are not about the order of interfaces but reducing the number of interfaces and avoid casting from object.
Yes, indeed Andy, this is also a very important point!