Alex Tsvetkov
Alex Tsvetkov

Reputation: 1659

C#: Why calling implemented interface method is faster for class variable than for interface variable?

I found this strange behaviour in .NET and even after looking into CLR via C# again I am still confused. Let's assume we have an interface with one method and a class that imlements it:

interface IFoo
{
    void Do();
}

class TheFoo : IFoo
{
    public void Do()
    {
        //do nothing
    }
}

Then we want just to instantiate this class and call this Do() method a lot of times in two ways: using concrete class variable and using an interface variable:

TheFoo foo1 = new TheFoo();

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (long i = 0; i < 1000000000; i++)
    foo1.Do();
stopwatch.Stop();
Console.Out.WriteLine("Elapsed time: " + stopwatch.ElapsedMilliseconds);

IFoo foo2 = foo1;

stopwatch = new Stopwatch();
stopwatch.Start();
for (long i = 0; i < 1000000000; i++)
    foo2.Do();
stopwatch.Stop();
Console.Out.WriteLine("Elapsed time: " + stopwatch.ElapsedMilliseconds);

Surprisingly (at least to me) the elapsed times are about 10% different:

Elapsed time: 6005
Elapsed time: 6667

The difference is not that much, so I would not worry a lot about this in most cases. However I just can't figure out why this happens even after looking in IL code, so I would appreciate if somebody point me to something obvious that I am missing.

Upvotes: 15

Views: 2925

Answers (2)

Hans Passant
Hans Passant

Reputation: 941327

You have to look at the machine code to see what is going on. When you do, you'll see that the jitter optimizer has completely removed the call to foo1.Do(). Small methods like that get inlined by the optimizer. Since the body of the method contains no code, no machine code is generated at all. It cannot make the same optimization on the interface call, it is not quite smart enough to reverse-engineer that the interface method pointer actually points to an empty method.

Check this answer for a list of the common optimizations performed by the jitter. Note the warnings about profiling mentioned in that answer.

NOTE: looking at the machine code in release build requires changing an option. By default the optimizer is disabled when you debug code, even in the release build. Tools + Options, Debugging, General, untick "Suppress JIT optimization on module load".

Upvotes: 18

Ivan Danilov
Ivan Danilov

Reputation: 14777

Well, compiler can't figure in general case, which actual method body should be executed when interface method is called because different classes could have different implementations.

So, when CLR faces interface call it sees at interface mappings of enclosing type and checks which concrete method it should call. It's lower than IL, actually.

UPD: IMO it is not the difference between call and callvirt.

What should CLR do when it encounters callvirt on a class type? Get type of the callee, look at its virtual methods table, find there method being called and call it.

What should it do when it encounters callvirt on an interface type? Well, in addition to prev points it should also check such things as explicit interface implementation. Because you COULD have two methods with identical signatures - one is class' method and other is explicit interface implementation. Such thing just do not exist when dealing with class types. I think it is the main difference here.

UPD2: Now I'm sure it is the case. See this for actual implementation detail.

Upvotes: 1

Related Questions