Calin said:
The c# code is compiled on end user machine which means that the windows version will probably be different depending on who's running the executable but what else might differ from one machine to another? What is the point of compiling the code "just in time"?
JIT-compilation has a variety of factors.
First, JIT-compilation makes the compiled intermediate code able to run on a variety of platforms. When you statically compile an exe in c++, this code can only run on a windows-machine, and only on certain supported architectures. If you instead compile a .NET-program, that program can run on any OS and architecture, as long as there is a matching .NET implementation. Windows, Linux, Mac. x86, x64, ARM… you name it. Of course there is still limiting factors, practically - mostly dealing with different hard-to-abstract OS specific system/library features, which means that pratically, you'd also need a framework that abstracts represeting pieces like the rendering-API; UI and so on… though it is still considerably less work than doing it yourself, which might require using silly stuff like NDK for compiling c++ on something like Android.
Second, this also usually recudes the time needed to compile such languages. Bytecode is way simpler then generation optimal machine-code, so the compiler has to spend less time during compilation to try to optimize sequences of code to their optimal forms. The JIT-compiler will still have to do those optimizations, but can delay or offload that. .NET is a bad example in this regard, as they do not have a native interpreted environment. Java for example runs on an interpreter, and only JIT-compiles code it considers benefical to do so. A one-time init function somewhere doesn't really need maximum speed. So it won't even bother to do that.
.NET on the other hand does need to compile all functions, but it can do so on-demand AFAIK. This still adds some overhead to executing the program on the users end (which can be seen famously in something like Unitys editor), but that'll even out over time.
Third, this does potentially allow additional optimizations, compared to a statically compiled program. When you compile your C++-program, the output is native code code that is run on a CPU. Thus, if you want to support ever CPU out there (which most compilers do), then you can only use those instruction-sets that are available everywhere. Newer or less common instructions are simply unavailable. Since the JIT is executed on the users PC, it can use exactly those instructions that the CPU has available, as long as the runtime accounted for them. One example is Intels new “APX” instructions for x86_64, which extends the instruction-set by a lot of useful stuff, like double the number of registers. It will take 10 years or more before C++-compilers can start support that (unless you only compile locally like with -march=native). But a JIT-compiler can implement it right away and make use of it. This has also been historically the case with things like AVE/SSE, which are now standard, but had to be introduces at a certain point before coming widespread supported.
On top of that, even the same instructions perform differently on different CPUs. Certain CPU (families) have specific sequences of instructions that perform better or worse, only on those specific CPUs. This is either due to errors on part of the manufacturers; missed optimizations, or simply new optimizations that have not been present before. Again, a statically compiled program that wants to support all CPUs needs to select a sequence of instructions that's likely to be best on average, or on most CPUs. A JIT can inspect exactly which CPU it is on, and apply it's knowledge about that CPUs flaws and strengths to optimize their output.
There's also a slew of other optimizations that are hard to achieve with static compilation. The JIT-compiler could determine branches or virtual calls on runtime, based on the loaded classes, or other global factors. Still, it seems most JIT compiled languages lack in performance compared to C++ from all what I've seen, so I'd personally label that as theoretical benefit in my book.
There's probably a lot more, like how bytecode can be smaller than native code when distributing; how it makes it easier to handle dependencies on external libraries (by not requiring a brittle binary interface like the one C++ (barely) uses)… But that's the gist of it.