AOT-компиляция происходит перед выполнением программы, не требует выделения дополнительной памяти и проходит с минимальной нагрузкой на систему.

Экономьте ресурсы и время с AOT-компиляцией в C#

Ahead-of-time компиляция была частью .NET ещё со времён выпуска первой версии .NET Framework. В .NET Framework была технология NGEN, которая предварительно генерировала нативный код и структуры данных во время установки программы .NET в глобальный кэш сборок. NGEN кэшировал код и структуры данных, необходимые среде выполнения для запуска установленной программы. Кэш был неполным – среда выполнения возвращалась к JIT-компиляции и загружалась по мере необходимости, но такой способ подходил для AOT-компиляции типичных приложений.

Среда выполнения Mono продолжила подход с кэшированием и дала возможность запуска без какой-либо генерации кода «на лету». Mono достигла этого благодаря вложениям в предварительно генерируемый код для универсальных шаблонов и различных заглушек, которые не были учтены в NGEN.

Несмотря на то, что такую компиляцию можно назвать досрочной, она отличается от аналогов в C, Go, Swift или в Rust. Рализация AOT в распространённых рантаймах .NET обладает рядом преимуществ, о которых далее пойдёт речь.

Распространённое заблуждение в различии ahead-of-time и just-in-time рантаймов состоит в том, что во внимание берётся только время генерации нативного кода. JIT-компилирующая среда выполнения сгенерирует нативный код по запросу, когда приложение развёрнуто и запущено в целевом окружении. AOT-компилирующий рантайм формирует нативный код предварительно, как часть сборки приложения.

Источник заблуждения кроется в старом способе реализации AOT-компиляции в мейнстримных рантаймах. Они добавляли нативный код в сборку .NET считая это хорошей практикой. AOT-компилятор способен на большее.

Мейнстримные рантаймы .NET исторически построены на концепциях, представленных в метаданных промежуточного языка потому, что они задумывались как динамические среды выполнения. Создание кэша было логичным шагом к эволюции рантаймов без тотальной перестройки.

Сохранение формата промежуточного языка затратно. Файлы в его формате создаются для независимости от железа и гибкости версий. Досрочная генерация кода позволяет отказаться от этих аспектов, но тогда зачем хранить существующие форматы?

Этим же вопросом задалась команда .NET около 11 лет назад. Тогда было ясно, что оптимизация существующей общеязыковой среды выполнения (CLR) для AOT будет недопустимо затратна. Так родилась новая среда выполнения, оптимизированная для AOT.

Анонсированный .NET Native дал прирост в 60% ко времени запуска по сравнению с NGEN. Эти улучшения стали возможными благодаря оптимизации среды выполнения и форматов файлов для AOT-компиляции.

Вот как выглядят программы в CoreRT после AOT-компиляции:

CoreRT

Заметьте, что такие вещи, как списки методов по типу и имена типов больше не существуют в данном формате. Они не нужны при AOT-компиляции программы. Реальные (не абстрактные) процессоры обрабатывают только код – им не нужно знать к какому типу принадлежит метод. Им также не нужна информация о количестве полей метода – они обращаются ко блоку памяти в определённом регистре.

Минимальные структуры данных в схеме – такие, как EEtype, описывающая тип System.String , содержат минимальное количество данных, необходимых для запуска программы .NET. Поле RelatedType в EEtype даёт возможность транслировать экземпляр System.String в System.Object . Слоты виртуальных таблиц поддерживают вызов виртуальных методов. BaseSize поддерживает распределение объекта и сборку мусора.

Декомпиляция программы в подобном представлении имеет сложность аналогичную декомпиляции в C++.

Структуры данных, которыми оперирует эта минимальная среда выполнения .NET по факту похожи на структуры данных, которыми бы оперировала библиотека выполнения среды C++ – то же самое касается и размера среды выполнения. В минимальной конфигурации CoreRT может компилировать автономный исполняемый файл размером до

400 kB, который включает полную среду выполнения и сборщик мусора (данные размера рассчитаны для x64 – целевые файлы x32 могут быть ещё меньше). В этой конфигурации используется сборщик мусора, который справляется с гигабайтами рабочей нагрузки в Azure.

Главное преимущество AOT-компиляции выражается во времени запуска. JIT-компиляция (или AOT рантайм, построенный на формате промежуточного языка) потратит значительное количество времени на поддержку запуска программы, но не на запуск кода. Процесс запуска будет выглядеть примерно так:

The startup path

60% улучшение времени запуска, которое наблюдалось в Универсальной платформе Windows с .NET Native, также переводится на другие типы рабочей нагрузки. Вот как выглядит время запуска для ASP.NET с эталонным тестом, который использует команда производительности CoreCLR:

ASP.NET

Время запуска не играет большой роли для «долгих» процессов, типа веб-серверов. Оно играет роль когда конечный пользователь сталкивается с задержками: время запуска – это тот момент времени, когда пользователь смотрит на песочные часы у курсора и на приложение на рабочем столе, на заставку в мобильном приложении или на пустую страницу в браузере.

Интересная метрика: сколько времени проходит с момента создания процесса до выполнения первой строки вашего Main () . Прежде, чем запустить выполнение кода, среде выполнения предстоит проделать много другой работы. Реализовать метрику довольно просто. Для этого первой строчкой в Main() напишите вызов к API times для Linux или GetProcessTimes для Windows.

Эти API предоставят вам информацию о количестве времени, потраченного на работу фреймворка прежде, чем он приступит к выполнению первой строки вашей программы. Для таких языков, как C этим значением обычно будет 0 – первая строка вашей программы запустится прежде, чем у ОС появится возможность обновить статистику. Для приложений командной строки время запуска обычно равняется нулю.

Вот, как выглядит время до первой инструкции в разных .NET рантаймах:

startup time

Значение CoreRT – 0. Приложение запускается также быстро, как в C.

Большое отличие между рантаймами JIT и AOT заключается в размерах развёрнутой автономной среды. CoreRT выигрывает тем, что рантайм написан на C#, в отличии от других .NET рантаймов, реализованных на C и C++. Управляемый код может быт отвязан, если не используется приложением. Для традиционных рантаймов часть среды выполнения обходится в фиксированный размер, который нельзя адаптировать для каждого приложения. AOT-среды могут быть гораздо меньше:

AOT size

В то время, как ЦП не нуждается в названиях ваших методов, а AOT-компилятор избавлен от необходимости транслировать эту информацию, API рефлексия позволяет найти любой тип, метод или поле по имени, и даёт доступ к дополнительной информации по этим сущностям, такой как подпись или имена параметров метода.

Компилятор CoreRT решает эту проблему попутной сортировкой информации рефлексии – она не обязательна для запуска программы и транслировать её не обязательно. Можно назвать дополнительные данные «сбором рефлексии». AOT-компилятор освобождает вас от затрат, если вы не используете данную функцию.

Без данных рефлексия становится ограниченной: можно использовать typeof, вызывать Object.GetType() , исследовать базовые типы и реализованные интерфейсы, но список методов, полей или названий типов становится невидимым.

reflection tax

Затраты на рефлексию остаются неисследованными в .NET: поскольку ни CoreRT, ни Mono не могут оперировать без метаданных промежуточного языка, отказ от метаданных невозможен для мейнстримных рантаймов. Однако, решение этого вопроса – прямой путь к развёрнутым средам размером менее мегабайта, что важно для таких задач, как WebAssembly.

Традиционно языки с AOT-компиляцией не имеют неограниченной рефлексии, как у .NET, и доказывают, что для завершения работы необязательно предоставлять всё рефлексии. Вещи, для которых сегодня используют рефлексию, могут быть сделаны без неё – во время сборки или компиляции. В этом отношении сборка обладает рядом преимуществ и для динамических рантаймов потому, что рефлексия медленна.

.NET обеспечивает некоторые удобства для генерации нового кода в рантайме. Будь то Reflection.Emit , Assembly.LoadFrom , или даже что-то настолько простое, как MakeGenericType и MakeGenericMethod . Эти формы представляют проблему для среды выполнения AOT, так как эти вещи нельзя сделать досрочно по определению, или не для всех программ.

Вот где AOT-рантайм вынужден совершать загрузку «на лету». Важно, что в среде выполнения AOT затраты на динамические функции происходят только при явном использовании: не вызываются динамические API, нет избыточного времени исполнения.

CoreRT – экспериментальная кроссплатформенная open-source среда выполнения .NET, заточенная на AOT-компиляции. Несмотря на свой экспериментальный статус, много частей CoreRT уже используются в поддерживаемых продуктах. CoreRT использует части .NET Native и складывает их воедино. Грубая процентная оценка долей, которыми CoreRT делится с CoreCLR и .NET Native:

Боремся с глобальным потеплением или AOT-компиляция в C#

Благодаря заимствованиям кода CoreRT улучшается каждый раз, когда улучшается CoreLib в CoreCLR или оптимизируется RyuJIT.

Характеристики производительности AOT- и JIT-компиляций можно сравнить с характеристиками электрического и бензинового двигателей в машинах.

  • Электрические двигатели производят движение, не тепло. JIT-скомпилированное приложение .NET затратит значительное количество ресурсов для поддержки вещей, необходимых для запуска кода, но не для самого запуска.
  • На низкой скорости электродвигатели обеспечивают больший крутящий момент, чем бензиновые, что даёт лучшее ускорение. В AOT-компилированном приложении пик производительности доступен сразу. Ваше приложение работает на полной скорости с самого начала. Однако, в конце бензиновый двигатель превзойдёт электрический также, как время исполнения JIT-компиляции опередит AOT.
  • Электрические двигатели проще. При замене стека технологий появляется много сложностей с JIT-компиляцией. Эти сложности затрагивают разработчиков среды выполнения и её пользователей. Направление запуска нативного кода определяется динамической настройкой, которую производил рантайм на основании предыдущих характеристик программы.

Бензиновый и электрический двигатели имеют своё собственное место. Всегда приятно иметь возможность выбора, и хорошо что в .NET есть такая возможность

Вы пропустили

AEGIS Algorithms Android Angular Apache Airflow Apache Druid Apache Flink Apache Spark API API Canvas AppSec Architecture Artificial Intelligence Astro Authentication Authorization AutoGPT AWS AWS Aurora AWS Boto3 AWS EC2 AWS Lambda Azure Babylon.js Backend bash Beautiful Soup Bento UI Big Data Binary Tree Browser API Bun Career Cassandra Charts ChatGPT Chrome Extension Clean Code CLI ClickHouse Coding Codux Combine Compose Computer Context Fusion Copilot Cosmo Route CProgramming cron Cryptography CSS CTF Cypress DALL-E Data Analysis Data science Database dbt dbt Cloud deno Design Design Patterns Detekt Development Distributed Systems Django Docker Docker Hub Drizzle DRY DuckDB Express FastAPI Flask Flutter For Beginners Front End Development Game Development GCN GCP Geospatial Git GitHub Actions GitHub Pages Gitlab GMS GoFr Golang Google Google Sheets Google Wire GPT-3 GPT3 Gradio Gradle Grafana Graphic Design GraphQL gRPC Guidance HMS Hotwire HTML Huawei HuggingFace IndexedDB InfoSec Interview iOS Jackknife Java JavaScript Jetpack Compose JSON Kafka Kotlin Kubernetes LangChain Laravel Linux LlaMA LLM localStorage Logging Machine Learning Magento Math Mermaid Micro Frontends Mobile Mobile App Development mondayDB MongoDB Mongoose MySQL Naming NestJS NET NetMock Networks NextJS NLP Node.js Nodejs NoSQL NPM OOP OpenAI OTP Pandas PDF PHP Playwright Plotly Polars PostgreSQL Prefect Productivity Programming Prometheus Puppeteer Pushover Python Pytorch Quarkus Rabbitmq RAG Ramda Raspberry Pi React React Native Reactor Redis REST API Revolut Riverpod RProgramming Ruby Ruby on Rails Rust Scalene SCDB ScyllaDB Selenium Servers Sklearn SLO SnowFlake Snowkase Software Architecture Software Development Solara Solid Spring Boot SQL SQLite Streamlit SudoLang Supabase Swift SwiftUI Tailwind CSS Taipy Terraform Testing Transformers TURN TypeScript Ubuntu UI Design Unix UX UX Design Vim Vite VSCode Vue Web Architecture Web Components Web Development Web Frameworks Web Scraping Web-разработка Webassembly Websocket Whisper Widgets WordPress YAML YouTube Zed Наука о данных Разное Тренды

Современный подход к разработке с использованием Next.js