JIT Compilation
Call `Greet()` a thousand times and it runs in microseconds, but the very first call is measurably slower. The native code for it did not exist yet, and something had to build it in that gap.
- ▸A method costs nothing to ship or load; it costs the first time you call it.
- ▸One method can run as two different pieces of machine code in a single program run.
- ▸The optimized version can beat a build-time compiler, because it sees which types actually showed up.
Here is a method and a loop that calls it ten times:
// Program.cs string Greet(string name) => $"Hello, {name}"; for (int i = 0; i < 10; i++) Greet("User");
The .dll you built holds Greet as IL, not native code, that was the previous topic. So when does the IL become machine code the CPU can run? Not at build, and, it turns out, not when the program starts either.
When a type is loaded, every method on it gets a tiny piece of code attached in front of it called a stub. The stub is not the method, it is a placeholder that knows how to get the method compiled. The first time Greet is actually called, control lands on that stub, the stub hands the method's IL to the just-in-time compiler (its code name is RyuJIT), and the compiler turns that one method's IL into native code right then, mid-execution.
That is the whole idea in one word: just-in-time. Compilation is lazy and per-method. A method that is never called is never compiled, the IL just sits in the assembly unused. And the work is scoped to exactly the method being called, not the whole file: calling Greet compiles Greet, and nothing else on the type moves until it too is called. The cost of that one-time compile is real but small, and it is paid on the first call, which is why that first call is the slow one in the loop above.