OOP Course
Сьогодні

Підрозділ 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 компілює методи під час виконання

Ключовий момент: 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 та 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-розігрів неприйнятний.

Розроблено Tomka Yurii · © 2026 ·