Reputation: 42443
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();
}
Upvotes: 3
Views: 671
Reputation: 1840
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.");
}
}
Upvotes: 4
Reputation: 64026
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.
Upvotes: 3
Reputation: 36402
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#
Upvotes: 0
Reputation: 625007
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.
Upvotes: 10
Reputation: 204129
Why not take a leaf out of Jeff's book and time both pieces of code like in this question?
Upvotes: 0
Reputation: 42037
Performance should be identical, whoever said otherwise must have been...confused.
Upvotes: 0