For one of my projects I'm using a lot of branches. Think:
if (foo) { do something }
else if (bar) { do something }
else if (bar2) { do something }
... and so on
and
if (foo) { do something }
if (bar) { do something }
if (bar2) { do something }
... and so on
What I've been wondering is if it makes sense to do sub-expression and/or logic eliminations to speed it up. For the sake of completeness, you can assume all this is in a single function. Say, if foo
and bar
have a sub-expression in common, you could write something like:
if (commonSubExpr)
{
if (foo without commonSubExpr) { do something }
if (bar without commonSubExpr) { do something }
}
if (bar2) { do something }
... and so on
Similarly, there are a lot of simple boolean logic rules that you can apply to optimize the rules.
My question is: does it make sense to do this at all? Or can I expect the JIT'ter to take care of this?
(According to the excellent article by Eric Lippert the compiler doesn't optimize this with the exception of constant folding - I assume this is still the case).
update+1
Okay, I shouldn't have asked if it makes sense, because now I get nice people trying to explain to me what premature optimizations are, which was not what I was after... my mistake. Just assume I know about over-designing, premature optimization, etc. please -- that's exactly what I'm attempting to avoid here.
So attempt 2... I want to know How Things Work and what I can expect from the compiler / JIT'ter.
I also noticed that some context might help here, so here's a little about the application:
In this case the application is a domain specific language that's compiled at run-time using Reflection.Emit to IL. There are good reasons I cannot use an existing language or an existing compiler. Performance is critical and after compilation a lot of operations are executed (which is why it's compiled to IL in the first place instead of simply interpreting the code).
The reason I'm asking this is because I want to know to what extend I should design the optimizer in the compiler. If the JIT'ter takes care of sub-expression elimination, I'll design the optimizer to do only basic things like constant folding, if .NET expects this to happen in the compiler, I'll design it in the compiler. Depending on what I can expect, the optimizer will have a completely different design. Since branches will probably be the most important performance drainers and because the implementation has a huge impact on my software design, I specifically decided to ask about this.
I know of no way to test this before implementing the compiler - which is quite a bit of work - so I wanted to get my basics straight before I started implementing. The reason I don't know how to test this is because I don't know in what cases the JIT'ter optimizes which pieces of code; I expect certain triggers in the .NET runtime that will result in certain optimizations (making test results unreliable)... If you know a way around this, please let me know.
The expressions foo
, bar
, etc. can be of any form you usually see in code, but you can assume it's a single function. So, it can be of the form if (StartDate < EndDate)
, but cannot be something like if (method1() < method2())
. To explain: in the latter case the compiler cannot simply make the assumption about the return values of the methods (you need to have information about the return values before you can optimize this), so sub-expression elimination is not trivial at all.
So, as an example of sub-expression elimination:
if (int1 < int2 && int1 < int3) {
//...
}
else if (int1 < int2 && int1 < int3) {
//...
}
can be rewritten to:
if (int1 < int2)
{
if (int1 < int3) {
//...
}
else if (int1 < int3) {
//...
}
}
So to conclude: What I want to know is if these kinds of sub-expression elimination optimizations make sense or not - or if they are handled by the JIT'ter.