views:

248

answers:

6

In replies to one of my questions, I received a number of answers saying that style 2 may perform better than style 1. I don't understand how, since I believe they should emit essentially the same machine instructions (if written in C++). Could you please explain why style 2 might perform better?

I'll rewrite the two styles here for easier reference:

Style 1:

while (!String.IsNullOrEmpty(msg = reader.readMsg()))
{
    RaiseMessageReceived();
    if (parseMsg)
    {
        ParsedMsg parsedMsg = parser.parseMsg(msg);
        RaiseMessageParsed();
        if (processMsg)
        {
            process(parsedMsg);
            RaiseMessageProcessed();
        }
    }
}

Style 2:

while (!String.IsNullOrEmpty(msg = reader.readMsg()))
{
    RaiseMessageReceived();
    if (!parseMsg) continue;

    ParsedMsg parsedMsg = parser.parseMsg(msg);
    RaiseMessageParsed();
    if (!processMsg) continue;

    process(parsedMsg);
    RaiseMessageProcessed();
}
A: 

Performance should be identical, whoever said otherwise must have been...confused.

Kim
+3  A: 

The best answer is to look at the generated byte-code/assembly and see. Then ignore what you see because an optimizing JIT compiler will change it anyway based on real-time analysis of the executing code. So stick with the style that best expresses the intent.

That said, style 2 should jump straight back to the condition, while style 1 could conceivably jump passed the if block only to hit another jump to the condition.

This has to be the best example of when not to prematurely optimize that I have seen.

Software Monkey
Actually the question is neither about style nor optimization. Style was discussed in my previous question, and I understand that optimization is irrelevant in this case. But I'm wondering about the actual output of the compiler/JIT.
Hosam Aly
You may not have explicitly said so, but IMO when you ask whether A will perform better than B, you are asking about an optimization concern. Which is the optimally performing code of two similar alternatives?
Software Monkey
A: 

Why not take a leaf out of Jeff's book and time both pieces of code like in this question?

Matt Hamilton
My question has a more theoritical nature, so timing is not what I'm looking for. My concern is that this code would (theoritically) be compiled to the same assembly if it were in C++, so I'm concerned about whether it wouldn't be the same case with C#!
Hosam Aly
@Hosma Aly, I don't get it. You want to know what performs better but timing is not what you are looking for?
tuinstoel
I'm more concerned about the theoretical output of the JIT, instead of the actual timing. Timing can tell me which one is faster, but it cannot tell *why* it is faster.
Hosam Aly
+9  A: 

I think performance would be negligibly different if it's different at all. A compiler may optimze them into the same form anyway.

The only substantive difference is stylistic.

I like style 1 just because the loop has one entry point (per iteration) and one exit point (per iteration) so it's easy to insert debug code at the end of the loop and know it'll get called. It's the same principle behind one entry and exit point on functions (for the same reasons). That being said though too much indenting can be hard to read so continue has its place as well.

cletus
Style was discussed in the other question, so it's not what I'm asking about here. I'm actually wondering about the output of the compiler and JIT.
Hosam Aly
And performance was covered: the difference is somewhere between completely negligible to complete nonexistant.
cletus
Thanks. :) I just wanted to clarify that I'm not asking about style, since some other answers may talk about it. You got my +1.
Hosam Aly
A: 

The code flow appears to be identical, and the bytecode should be the same.

Disclaimer: I am a C/C++ programmer, I don't really do C#

Hasturkun
+3  A: 

I had to check this.

Here is my version of the code:

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Tester t=new Tester();
            t.Method1(new Stack<string>(), new MsgParser(), true, true);
            t.Method2(new Stack<string>(), new MsgParser(), true, true);
        }
    }
    class Tester
    {
        public void Method1(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
        {
            string msg;
            while (!String.IsNullOrEmpty(msg = strings.Pop()))
            {
                RaiseMessageReceived();
                if (parseMsg)
                {
                    ParsedMsg parsedMsg = parser.ParseMsg(msg);
                    RaiseMessageParsed();
                    if (processMsg)
                    {
                        process(parsedMsg);
                        RaiseMessageProcessed();
                    }
                }
            }
        }

        public void Method2(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
        {
            string msg;
            while (!String.IsNullOrEmpty(msg = strings.Pop()))
            {
                RaiseMessageReceived();
                if (!parseMsg) continue;

                ParsedMsg parsedMsg = parser.ParseMsg(msg);
                RaiseMessageParsed();
                if (!processMsg) continue;

                process(parsedMsg);
                RaiseMessageProcessed();
            }

        }

        private void RaiseMessageProcessed()
        {
            Console.WriteLine("Done");
        }

        private void process(ParsedMsg msg)
        {
            Console.WriteLine(msg);
        }

        private void RaiseMessageParsed()
        {
            Console.WriteLine("Message parsed");
        }

        private void RaiseMessageReceived()
        {
            Console.WriteLine("Message received.");
        }
    }

    internal class ParsedMsg
    {
    }

    internal class MsgParser
    {
        public ParsedMsg ParseMsg(string msg)
        {
            return new ParsedMsg();
        }
    }
}

I built it with code optimization (default Release configuration), and disassembled the assembly using Reflector. The result verifies that the two styles are identical:

internal class Tester
{
    // Methods
    public void Method1(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
    {
        string msg;
        while (!string.IsNullOrEmpty(msg = strings.Pop()))
        {
            this.RaiseMessageReceived();
            if (parseMsg)
            {
                ParsedMsg parsedMsg = parser.ParseMsg(msg);
                this.RaiseMessageParsed();
                if (processMsg)
                {
                    this.process(parsedMsg);
                    this.RaiseMessageProcessed();
                }
            }
        }
    }

    public void Method2(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
    {
        string msg;
        while (!string.IsNullOrEmpty(msg = strings.Pop()))
        {
            this.RaiseMessageReceived();
            if (parseMsg)
            {
                ParsedMsg parsedMsg = parser.ParseMsg(msg);
                this.RaiseMessageParsed();
                if (processMsg)
                {
                    this.process(parsedMsg);
                    this.RaiseMessageProcessed();
                }
            }
        }
    }

    private void process(ParsedMsg msg)
    {
        Console.WriteLine(msg);
    }

    private void RaiseMessageParsed()
    {
        Console.WriteLine("Message parsed");
    }

    private void RaiseMessageProcessed()
    {
        Console.WriteLine("Done");
    }

    private void RaiseMessageReceived()
    {
        Console.WriteLine("Message received.");
    }
}
Øyvind Skaar
The code optimalization doesn't actually do anything here. The results are the same compiling in Debug configuration.
Øyvind Skaar
Thanks. I really appreciate your effort. But, for the sake of completeness, I guess the JIT may have something to do with this code. Do you think it would make any difference?
Hosam Aly
That may be the case. The IL code is not identical for the two methods.
Øyvind Skaar