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.
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”.