0

If I implement an interface in a class, that does absolutely nothing, does it slow down code it is called from? Will example 2 (NoLogger) have any influence of the speed of the code, it is used in?

Example code:

interface ILogger{
    void Write(string text);
}

class TextLogger : ILogger {
    public void Write(string text){
        using (var sw = new StreamWriter(@"C:\log.txt"))
        {
            sw.WriteLine(text);
        }
    }
}

class NoLogger : ILogger{
    public void Write(string text){
        //Do absolutely nothing
    }
}

Implementation 1, TextLogger

void Main(){
        ILogger tl = new TextLogger();
        for (int i = 0; i < 100; i++)
        {
            tl.Write(i.ToString());
        }
 }

Implementation 2, NoLogger

void Main(){
        ILogger tl = new NoLogger();
        for (int i = 0; i < 100; i++)
        {
            tl.Write(i.ToString());
        }
 }

Of course example 1 (textlogger) slows down execution of the code it is implemented in, because it actually does something.

But what about example 2? Is the compiler intelligent enough to figure out, that even though a class is instantiated and a method is called, there is absolutely no code that does anything down any path, and just ignores it at compile time?

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
Kjensen
  • 12,447
  • 36
  • 109
  • 171
  • If you build with optimizations (Release mode), the calls to `Write` will probably be _inlined_, so the stack won't have to deal with an extra "frame" for the method call. When the body is empty, the inlining might mean that nothing is done. However, the call to the method is not removed _by the C# compiler_. – Jeppe Stig Nielsen Nov 01 '13 at 12:37

3 Answers3

2

I think we can generalise this to "will the JIT ever inline the virtual call of an interface method" - to which I strongly suspect the answer is "no" (to do that, it would need to prove that the implementing type involved can only ever be that one concrete type, and that is more analysis than I would ever expect it to do).

Of course, you could run it 500,000,000 times with the call, and with nothing - and then you'd have a reasonable starting place at an answer.

Also note: even if the Write does nothing: it is still required to execute the i.ToString(), because that could have side effects.

I suspect you should look at the [Conditional("...")] attribute. This does change things. A lot. For example:

public static class Logger
{
    [Conditional("TRACE")]
    public static void Write(string text)
    {
       // some stuff
    }
}

Now; if we compile this without the TRACE symbol defined:

static void Main()
{
    for (int i = 0; i < 100; i++)
    {
        Logger.Write(i.ToString());
    }
 }

our code is compiled as if it was:

static void Main()
{
    for (int i = 0; i < 100; i++)
    {
    }
 }

The call is removed, but also: any parameter evaluations (the i.ToString()) are also removed. If we compile with the TRACE symbol defined - then the code exists.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I don't know whether the .NET JIT does it, but many JIT compilers, for Java and for dynamic languages, do inline virtual calls and perform other optimizations that are conditional and may have to be reverted later. –  Nov 01 '13 at 12:39
2

Is the compiler intelligent enough to figure out, that even though a class is instantiated and a method is called, there is absolutely no code that does anything down any path, and just ignores it at compile time?

The compiler cannot legally eliminate the call completely. At the very least, the compiler must evaluate all the expressions that you pass to the method being called - specifically, in your example i.ToString() will be called 100 times, regardless of what the implementation actually does. The compiler cannot optimize it out, because otherwise it might accidentally change semantics of the program in cases when parameter expressions have side effects.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    Determining that `i.ToString()` has no side effect is an easy exercise compared to determining that the `Write` call does nothing. You're right that the argument evaluation code can't be dropped thoughtlessly, but that too is code and can be optimized. –  Nov 01 '13 at 12:41
  • 1
    The things to compare would be: Will the code `for (int i = 0; i < 100; i++) { tl.Write(i.ToString()); }` where we know `Write` is an empty method run slower than the code `for (int i = 0; i < 100; i++) { i.ToString(); }`? And does it matter if `Write` is an interface method or a really non-vritual method? – Jeppe Stig Nielsen Nov 01 '13 at 12:50
2

There is an interesting blog entry here that describes that the .net JIT compiler does dynamic profiling to figure out if you use call the same virtual function over and over again, and then devirtualizes and possibly inlines it. To this end, the code is being patched while the application is running.

So it will not evaluate to a no-op, but the JIT compiler will eliminate most of the overhead associatated with calling a method.