General
In your regular compiler, the generated code will most often be the same, at least when assuming that you are using regular
csc.exe /optimize+
cl.exe /O2
g++ -O2
and related default optimization modes.
The general mantra is: profile, profile, profile (and don't micro-optimize until your profiler tells you to). You can always look at the generated code2 to see whether there is room for improvement.
Think of it this way, e.g. the C# code:
C#/.NET
Each of your complexExpressions is a de-facto function call invocation (call, calli, callvirt opcode3) that requires it's arguments to be pushed onto the stack. The return value will be left pushed onto the stack instead of the parameters on exit.
Now, CLR being a stack-based virtual machine (i.e. register-less) this amounts to exactly the same as an anonymous temporary variable on the stack. The only difference is the number of identifiers used in the code.
Now what the JIT engine does with that is another matter: the JIT engine will have to translate these calls into native assembly and may be doing optimization by tweaking register-allocation, instruction ordering, branch prediction and stuff like that1
1 (though in practice, for this sample it won't be allowed to do the more interesting optimizations because the complex function calls
may have sideeffects and the C# specs are very clear about the evaluation order and so-called sequence)point. Note however, that the JIT engine is allowed to inline function calls, to reduce call overhead.
Not only when they are non-virtual, but (IIRC) also when the runtime type can be known statically at compile time for certain .NET framework internals. I'd have to look up a reference for this, but in fact I think there are attributes introduced in .NET framework 4.0 to explicit prevent inlining of framework functions; this is so that Microsoft can patch library code in service packs/updates, even when user assemblies have been ahead-of-time compiled (ngen.exe) into native images.
C/C++
In C/C++ the memory model is much more lax (i.e. at least until C++11) and the code is usually compiled down to native instructions at compile time directly. Add that C/C++ compilers usually do aggressive inlining, the code even in such compilers will usually be the same, unless you compile without optimization enabled
2 I use
ildasm
or monodis
to see the IL code generated
mono -aot=full,static
or mkbundle
to generate native object modules and objdump -CdS
to see the annotated native assembly instructions for that.
Note this is purely curiosity, because it rarely happens that I find interesting bottlenecks that way. See however, Jon Skeet's blog posts on performance optimizing Noda.NET
for good examples of surprises that may lurk in the generated IL code for Generic classes.
3 Edit not accurate for operators on compiler intrinsics, though even they will just leave their result on the stack.
&&
actually compile into a branch too? – Petras