Lately Twitter has been quite a source of inspiration. This time the guilty thread is this one. Javier Cantón made a comment about the performance difference between calling a virtual method versus calling the same method through an interface.
In this post I’m not going to talk about how evil premature optimizations are; I assume Javier’s comment had its context, so I’m going to stick with the part I found curious. Luis Ruiz Pavón told us to do this kind of measurement with BenchmarkDotNet and, since a couple of questions had come up for me, I got down to it.
Important warning: the actual numbers in the measurements don’t matter; every computer will take a different number of nanoseconds to perform each operation. What matters here is looking at the time difference needed to run the method in each situation.
Direct call to a non-virtual method
Javier said that in his context he cared about calls to virtual methods vs interfaces, leaving out direct calls to a non-virtual method. Still, to give a bit of context on performance, I thought it was interesting to measure it. A direct call to a non-virtual method takes so little time that sometimes it can’t measure it and tells me it takes 0.0000 nanoseconds. In some of the runs the median was 0.0025 ns. This result is actually because the JIT is inlining the method. If we decorate the method with the [MethodImpl(MethodImplOptions.NoInlining)] attribute so it doesn’t inline it (thanks Juanma), the median is 1.96 ns.
Direct virtual vs Interface
On my machine, a direct call to a virtual method takes about 2.91 nanoseconds. If we make the same call through an interface, it needs 3.82 nanoseconds per operation.
As I said before, I’m not going to judge whether saving 1 nanosecond per method call is important or not; it’s a matter of context, and I’m sure there are contexts where it can be important.
If we assume we’re in a context where we really care about not paying this penalty, we could rule out using interfaces, but that can constrain our design in many ways.
Abstract classes
Thinking about how I could get around that problem and get performance like a direct call but with the advantages of having an interface, I came up with the idea of using an abstract class. It’s true that using an abstract class isn’t exactly the same as using an interface, but even so I think it shows intent much better than resorting to a virtual method.
The question was what the performance of a call through an abstract class would be. Would it be the same as an interface?
Well, it turned out it wasn’t. According to my measurements, each call takes about 2.88 nanoseconds. What’s more, whether I called through the abstract class or through the implementation of the abstract class, the times were the same.
Careful, though: even if at first it looks like it’s even faster than a direct call to a virtual method, I think the difference is negligible, and if I repeated the measurements several times, they’d take turns being the fastest 😉
Reflection
While I was at it, out of curiosity I measured the time of making the calls using reflection, assuming I had the MethodInfo cached and measuring only the method’s Invoke. Each call took 272.87 nanoseconds, a world apart from the other mechanisms.
A bit more detail
Juanma pointed out, and he’s absolutely right, that for such tiny measurements it’s useful to use histograms. BenchmarkDotNet doesn’t currently have that feature, but what it does provide is the standard deviation, which can be useful for interpreting the data. Here are the results of the last measurement:
| Method | Median | Deviation |
|---|---|---|
| Direct non-virtual | 0.0025 ns | 0.0853 ns |
| Direct non-virtual no inline | 1.9673 ns | 0.1040 ns |
| Direct virtual | 2.9151 ns | 0.0917 ns |
| Interface | 3.8213 ns | 0.1464 ns |
| Abstract | 2.8829 ns | 0.1113 ns |
| Direct with reflection | 272.8738 ns | 17.7846 ns |
Conclusion
If we ever care that much about performance, we can use an abstract class instead of an interface (yes, yes, I already said it’s not the same thing).
Any excuse is good to rack your brain a bit and dig up some quirky data.
This kind of post is practically useless, but datap0rn has its charm and I had a lot of fun writing it.
BenchmarkDotNet rocks, although it takes forever to run each measurement 😀