5

I have an interface I, an abstract class A, concrete classes for both of those, and a concrete class C to use as a baseline.

interface I
{
    void Do();
}
abstract class A
{
    public abstract void Do();
}
class C
{
    public void Do() {}
}
class IImpl : I
{
    public void Do() {}
}
class AImpl : A
{
    public override void Do() {}
}

I benchmarked calls to C.Do(), I.Do() and A.Do() using BenchmarkDotNet.

public class Bench
{
    private C _c;
    private I _i;
    private A _a;

    [Setup]
    public void Setup()
    {
        _c = new C();
        _i = new IImpl();
        _a = new AImpl();
    }

    [Benchmark(Baseline = true)]
    public void ConcreteCall()
    {
        _c.Do();
    }
    [Benchmark]
    public void InterfaceCall()
    {
        _i.Do();
    }
    [Benchmark]
    public void AbstractCall()
    {
        _a.Do();
    }
}

The benchmark results consistently show the call to A.Do() to be faster than I.Do().

        Method |      Mean |    StdErr |    StdDev | Scaled | Scaled-StdDev |
-------------- |---------- |---------- |---------- |------- |-------------- |
  ConcreteCall | 0.0673 ns | 0.0114 ns | 0.0440 ns |   1.00 |          0.00 |
 InterfaceCall | 1.4944 ns | 0.0084 ns | 0.0325 ns |  62.92 |         74.68 |
  AbstractCall | 0.8178 ns | 0.0139 ns | 0.0539 ns |  34.43 |         40.99 |

I tried a few variations on this structure, such as having a single Impl class derived from both A and I, or linearising the hierarchy by having A implement I, and reproduced the same effect every time.

The difference isn't much, of course - what's 700 picoseconds between friends? - but I'd like to understand what's going on in the .NET runtime. I understand why C.Do() is the fastest by far - there's no indirection involved in looking up the method implementation - but why does the call via the abstract class beat the call via the interface? I'm using .NET Core 1.1 on OSX with x64 RyuJit.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • 2
    You are a bit off, it is 700 picoseconds. What's a million between friends ;) Benchmark is a bit off too, but very fast code like this is very hard to measure, the ConcreteCall should be 0. An abstract call cannot be optimized away, you measure the cost on an indirect CALL instruction. And sure, an interface call has a bit of extra overhead due to the required stub that binds the interface call to the method, one extra JMP instruction. All normal, nothing to worry about. – Hans Passant Feb 26 '17 at 16:48
  • @HansPassant Whoops, haha! Fixed. Could you elaborate a bit more on your comment in an answer, perhaps? – Benjamin Hodgson Feb 26 '17 at 16:52
  • 2
    Answers work best when there is an actual problem to be solved or a concrete question asked. I don't see any. Maybe you ought to take a look at [this post](http://stackoverflow.com/a/42187448/17034), shows you how to visualize the generated machine code in the debugger. – Hans Passant Feb 26 '17 at 17:00
  • 1
    @PeterDuniho I don't think this is a duplicate. The linked question is about a direct call vs a virtual call. This one is about two different types of virtual call. – Benjamin Hodgson Feb 26 '17 at 19:14
  • It's all the same thing. The "direct" call in the other post is still virtual dispatch, just like the abstract method here. I.e. _"two different types of virtual call"_. – Peter Duniho Feb 26 '17 at 19:17
  • @PeterDuniho Ah, gotcha. I'd misread the original. Thanks! – Benjamin Hodgson Feb 26 '17 at 19:18

0 Answers0