As a rule, code in the where or let block of a constant applicative form is evaluated only once, and only as deep as necessary (i.e., if it's not used at all it also won't be evaluated at all).
f is not a constant applicative form because it has arguments; it's equivalent to
f' = \a b -> let e1 = <lengthy computation>
in if a==b
then <simple computation>
else e1 + 2 * e1 - e1^2
So, e1 is evaluated once every time you call the function with both arguments. This is likely also what you want, and in fact the best behaviour possible if <lengthy computation> depends on both a and b. If it, say, only depends on a, you can do better:
f₂ a = \b ->
if a==b then <simple computation>
else e1 + 2 * e1 - e1^2
where e1 = <lengthy computation>
This form will be more efficient when you do e.g. map (f 34) [1,3,9,2,9]: in that example, e1 would only be computed once for the entire list. (But <lengthy computation> won't have b in scope, so it can't depend on it.)
OTOH, there can also be scenarios where you don't want e1 to be kept at all. (E.g. if it occupies a lot of memory, but is rather quick to compute). In this case, you can just make it a “nullary function”
f₃ a b
| a==b = <simple computation>
| otherwise = e1() + 2 * e1() - e1()^2
where e1 () = <lengthy computation>
Functions are not memoized by default, so in the above, <lengthy computation> is done zero times if a==b and three times else.
Yet another possibility is to force that e1 is always evaluated exactly once. You can do that with seq:
f₄ a b = e1 `seq` if a==b
then <simple computation>
else e1 + 2 * e1 - e1^2
where e1 = <lengthy computation>
This is the only of the suggestions that actually changes something about the semantics, not just the performance: assume we define always e1 = error "too tough". Then f, f', f₂ and f₃ will all still work provided that a==b; however f₄ will even fail in that case.
As for optimisations (-O or -O2) – these generally won't change anything about the strictness properties of your program (i.e. can't change between the behaviour of f and f₄). Beyond that, the compiler is pretty much free to make any change it considers benefitial to performance. But usually, it will not change anything about what I said above. The main exception, as Taren remarks, is something like f₃: the compiler will readily inline e1 () and then share a reference to the computed value, which prevents the garbage collector from reclaiming the memory. So it's better not to rely on this (anyway somewhat hackish) technique.