I use both C++ and C# and something that’s been on my mind is whether it’s possible to use generics in C# to elide virtual function calls on interfaces. Consider the following:
int Foo1(IList<int> list)
{
int sum = 0;
for(int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
int Foo2<T>(T list) where T : IList<int>
{
int sum = 0;
for(int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
/*...*/
var l = new List<int>();
Foo1(l);
Foo2(l);
Inside Foo1, every access to list.Count and list[i] causes a virtual function call. If this were C++ using templates, then in the call to Foo2 the compiler would be able to see that the virtual function call can be elided and inlined because the concrete type is known at template instantiation time.
But does the same apply to C# and generics? When you call Foo2(l), it’s known at compile-time that T is a List and therefore that list.Count and list[i] don’t need to involve virtual function calls. First of all, would that be a valid optimization that doesn’t horribly break something? And if so, is the compiler/JIT smart enough to make this optimization?
This is an interesting question, but unfortunately, your approach to “cheat” the system won’t improve the efficiency of your program. If it could, the compiler could do it for us with relative ease!
You are correct that when calling
IList<T>through an interface reference, that the methods are dispatched at runtime and therefore cannot be inlined. Therefore the calls toIList<T>methods such asCountand the indexer will be called through the interface.On the other hand, it is not true that you can achieve any performance advantage (at least not with the current C# compiler and .NET4 CLR), by rewriting it as a generic method.
Why not? First some background. The C# generics work is that the compiler compiles your generic method that has replaceable parameters and then replaces them at run-time with the actual parameters. This you already knew.
But the parameterized version of the method knows no more about the variable types than you and I do at compile time. In this case, all the compiler knows about
Foo2is thatlistis anIList<int>. We have the same information in the genericFoo2that we do in the non-genericFoo1.As a matter of fact, in order to avoid code-bloat, the JIT compiler only produces a single instantiation of the generic method for all reference types. Here is the Microsoft documentation that describes this substitution and instantiation:
This means that the JIT compiler’s version of the method (for reference types) is not type safe but it doesn’t matter because the compiler has ensured all type-safety at compile time. But more importantly for your question, there is no avenue to perform inlining and get a performance boost.
Edit: Finally, empirically, I’ve just done a benchmark of both
Foo1andFoo2and they yield identical performance results. In other words,Foo2is not any faster thanFoo1.Let’s add an “inlinable” version
Foo0for comparison:Here is the performance comparison:
So you can see that
Foo0, which can be inlined, is dramatically faster than the other two. You can also see thatFoo2is slightly slower instead of being anywhere near as fast asFoo0.