Підрозділ 1.4
JIT-компіляція
Пояснює JIT-компіляцію, проміжну мову CIL і те, як C#-код перетворюється на машинні інструкції під час виконання.
1.4. JIT-компіляція
Код C#, написаний програмістом, не виконується процесором безпосередньо. Він проходить два окремих етапи перетворення, перш ніж стати машинними інструкціями. Розуміння цих етапів допомагає пояснити, чому .NET поєднує кросплатформовість з високою продуктивністю.
Два кроки до виконання
Крок 1 — компілятор C# (під час розробки):
Файл .cs обробляє компілятор і перетворює його на CIL (Common Intermediate Language) — проміжну мову платформи. Результат зберігається у збірці: файл .dll або .exe. CIL — це не машинний код жодного конкретного процесора; це платформонезалежні інструкції, зрозумілі CLR.
Разом із CIL збірка містить метадані — повний опис типів, методів, полів, параметрів. Завдяки метаданим CLR розуміє структуру коду без потреби в заголовкових файлах (на відміну від C++).
Крок 2 — JIT-компілятор (під час виконання):
JIT (Just-In-Time) — «саме вчасно». Коли програма запущена і певний метод викликається вперше, JIT-компілятор перетворює його CIL на машинний код конкретного процесора. Скомпільований код кешується: при повторних викликах він виконується напряму, без повторної компіляції.
Покроковий процес JIT

Ключовий момент: JIT не компілює всю програму при запуску. Він компілює лише ті методи, які фактично викликаються під час конкретного сеансу роботи. Методи, до яких програма не звертається, залишаються у вигляді CIL і не витрачають час на компіляцію.
Демонстрація: що відбувається при запуску
Перший виклик SayHello() запускає JIT-компіляцію цього методу. Другий виклик — вже йде напряму у нативний код.
CIL і метадані — що всередині збірки
Збірка .dll — це не просто набір інструкцій. Вона містить дві принципово різні речі:
| Що | Для чого |
|---|---|
| CIL-інструкції | Платформонезалежний проміжний код, що JIT перетворює у машинний |
| Метадані | Опис типів, методів, полів, параметрів, атрибутів |
Метадані — це те, що дозволяє C# мати рефлексію: програма може запитати у CLR «які методи є у цьому класі?» прямо під час виконання:
Це можливо саме завдяки метаданим у збірці. У мовах без керованого середовища (C, C++) такої можливості за замовчуванням немає.
Переваги JIT-підходу
- Кросплатформовість: одна і та сама збірка запускається на будь-якій ОС, де є .NET runtime
- Адаптація до CPU: JIT знає, на якому процесорі запускається код, і може використовувати специфічні інструкції (AVX, SSE)
- Ощадливість: компілюється лише код, що реально виконується
- Оптимізації runtime: JIT може застосовувати оптимізації (inlining, devirtualization), які недоступні статичному компілятору
JIT vs AOT

Окрім JIT, у сучасному .NET існує AOT (Ahead-Of-Time) — компіляція наперед, ще до запуску:
- JIT: збірка з CIL розповсюджується, машинний код генерується на машині користувача під час виконання
- AOT: нативний бінарний файл генерується заздалегідь; при запуску нічого компілювати не треба
AOT корисний для:
- AWS Lambda / хмарні функції — де важливий час холодного старту
- .NET MAUI і iOS — де JIT взагалі заборонений платформою
- Blazor WebAssembly — де код виконується у браузері
- CLI-утиліт — де потрібен один нативний бінарний файл без залежностей
Для більшості серверних і консольних застосунків JIT є стандартним і оптимальним вибором. AOT потрібен у специфічних сценаріях і вносить певні обмеження (наприклад, на рефлексію).
NativeAOT — нативний бінарний файл без JIT
NativeAOT (.NET 7+) — спеціальний режим публікації, що компілює весь застосунок у нативний машинний код заздалегідь, ще до розгортання. Результат — єдиний виконуваний файл без залежності від .NET runtime на машині користувача.
Щоб опублікувати застосунок у режимі NativeAOT:
<!-- У файлі .csproj -->
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>dotnet publish -r win-x64 -c Release
# або
dotnet publish -r linux-x64 -c ReleaseРезультат — один нативний .exe (на Windows) або бінарний файл (на Linux), що запускається без встановленого .NET.
| Показник | JIT | NativeAOT |
|---|---|---|
| Час старту | Сотні мс (JIT cold start) | < 10 мс |
| Пам'ять | Більше (CLR, JIT-інфраструктура) | Значно менше |
| Розмір артефакту | Мала збірка + великий runtime | Один великий файл |
| Рефлексія | Повна підтримка | Обмежена (потрібні Source Generators) |
| Динамічний код | Assembly.Load, Activator.CreateInstance |
Не підтримується |
| Платформа | Кросплатформово (одна збірка) | Окремий бінарний для кожної ОС/CPU |
Коли NativeAOT вигідний:
- Мікросервіси і serverless (AWS Lambda, Azure Functions): час холодного старту критичний
- CLI-утиліти: один файл без залежностей, зручно розповсюджувати
- IoT та вбудовані системи: обмежена пам'ять, відсутність .NET runtime
- Мобільні платформи (iOS): JIT заборонений платформою
Обмеження NativeAOT:
NativeAOT не може компілювати код, що залежить від рефлексії з невідомими типами. Бібліотеки, що аналізують типи через typeof(T) у рантаймі, потребують адаптації. Саме тому JSON Source Generators (19.1) і інші генератори розроблені як AOT-сумісна альтернатива до рефлексивного підходу.
Підсумок
| Поняття | Суть |
|---|---|
| CIL | Проміжна мова; результат роботи компілятора C# |
| Збірка (.dll/.exe) | CIL + метадані; кросплатформовий артефакт розгортання |
| Метадані | Опис типів і методів; основа рефлексії та перевірки типів |
| JIT | Перетворює CIL у машинний код під час першого виклику методу |
| Кеш JIT | Скомпільований код зберігається; повторні виклики — без JIT |
| AOT | Альтернатива JIT: компіляція до запуску; нативний бінарний файл |
| NativeAOT | AOT-режим .NET 7+: один нативний файл, швидкий старт, без JIT, без runtime |
C#-код проходить шлях: .cs → компілятор → CIL у збірці → JIT при виконанні → машинний код процесора. Завдяки цьому ланцюжку .NET поєднує зручність високорівневої мови, кросплатформовість і продуктивність, близьку до нативного коду. NativeAOT — альтернативний шлях для сценаріїв, де JIT-розігрів неприйнятний.