home mail me! RSS (2.0) feed

Sealed overridden methods in Mono - faster?

I had a discussion with some friends at Lab49 about the merits of sealed overridden methods in C# (and .NET) and the discussion ended with some comments about it probably not helping with performance at least.

The problem is that I do remember having seen that the JIT for .NET creates more efficient code for sealed methods, but now decided to try it out in Mono. I used the regular profiler for Mono, compiling with the 2.0 compiler (gmcs) and got quite a large difference between the invocations.

I invoked an empty method 800 million times, using a simple triad of classes, with one parent and two children, one using a sealed overridden method and the other a regular override. Did I optimize the code? Nope, just a regular compilation.

In the polymorphic case, where we have a reference of the type Parent to both parent and children, the invocation of the parent and sealed overridden method were about as fast, and both about twice as fast as calling the non-sealed overridden method. A bit strange, since my intuition was that they would be equally fast.

What about the monomorphic case then, when we have the actual run-time type in our hands - and, more importantly, in the competent hands of a compiler/JIT system?

Having the type at one’s disposal did nothing for the regular override, which is not that strange, since the compilation system does not know whether it might have been overridden further in some grandchild. And, equally non-surprising, it did have an effect on the sealed version. In fact, the sealed version was then almost twice as fast as the regular override.

To summarize, using sealed overridden methods in Mono, with the C# 2.0 compiler and no further optimization, is considerably faster than using regularly overridden methods.

Some metrics for the loops, in some units (think nanosecond…), including the loop iterations themselves (which seem to vary between 22 and 63 units)

  • Call to Parent.Foo: 342
  • Call to NonSealed.Foo using Parent as (polymorphic) type: 577
  • Call to Sealed.Foo using Parent as (polymorphic) type: 392
  • Call to NonSealed.Foo using direct, monomorphic type: 541
  • Call to Sealed.Foo using direct, monomorphic type: 294

Finally, the actual test code.


public class Parent {
public virtual void Foo() {
}
}

public class NonSealed : Parent {
public override void Foo() {
}
}

public class Sealed : Parent {
public sealed override void Foo() {
}
}

public class SealedChild : Sealed {
}

public class NonSealedChild : NonSealed {
}

public class MainClass {

const int COUNT = 800000000;

static void loop(DeclType obj, RealType dummy)
where DeclType : Parent {
for (int i = 0; i < COUNT; ++i)
obj.Foo();
}

static void Main(string[] arr) {
new Parent().Foo();
new Sealed().Foo();
new NonSealed().Foo();
loop(new Parent(), (Parent)null);
loop(new NonSealed(), (NonSealed)null);
loop((Parent)new NonSealed(), (NonSealed)null);
loop(new Sealed(), (Sealed)null);
loop((Parent)new Sealed(), (Sealed)null);
}
}

Leave a Comment

You must be logged in to post a comment.