⚡️ JIT: из IL в инструкции процессора
В прошлый раз мы прогнали код через конвейер компилятора и остановились на IL — промежуточном переносимом коде внутри .dll 📦
Но процессор про IL ничего не знает, ему нужны его собственные инструкции. Вот тут-то JIT и делает этот последний перевод
🕐 Кто такой JIT
JIT — Just-In-Time, «точно в срок». Это ещё один компилятор, но работает он не при сборке, а уже во время выполнения программы: берёт IL и превращает его в машинный код процессора, на котором прямо сейчас выполняется программа🤩
JIT делает это лениво, по методам. То есть, компилируется не весь .dll разом, а конкретный метод в момент своего первого вызова. Дальше уже используется его машинный код 🐁
🛠 Это компилятор, а не переводчик слов
JIT — это именно компилятор. Он не идёт по IL шаг за шагом, как интерпретатор, и не перекладывает одну инструкцию в другую один-в-один. Он подбирает инструкции текущего процессора, раскладывает значения по регистрам, кое-где оптимизирует
🔎 Смотрим своими глазами
Возьмём наш однострочник. В IL он был представлен тремя инструкциями: ldstr, call, ret. После обработки JIT-ом под x64 ЦПУ они превращаются в настоящий ассемблер (дикпик 1):
ldstr стал mov — кладём адрес нашей строки в регистр, откуда его заберёт вызов. call остался call, ret остался ret. А ещё JIT сам дописал пролог и эпилог (push/add вокруг) — это возня со стеком под вызов, которой в IL не было 🧹
Вот это уже те самые инструкции, которые процессор исполняет напрямую⚡️
Этот машинный код никуда не сохраняется на диск — JIT кладёт его прямо в память процесса, и с концом программы он исчезает. Почему так и чем за это платим — вернёмся, когда дойдём до памяти 🧠
🅰️ Что с этим делать
🟢 Первый вызов метода всегда чуть дороже: ровно в этот момент JIT его и компилирует. Из-за этого возникает такое явление, как «холодный старт», когда самые первые прогоны методов медленнее последующих
🟢 IL не исполняется сам по себе. Он лишь заготовка, которую JIT собирает в машинный код под запуск. Дальше уже работает этот нативный код
Причём JIT в первый раз переводит метод наспех, лишь бы программа уже начала выполняться. А если метод оказывается «горячим», то есть если он вызывается снова и снова, то в какой-то момент JIT компилирует его заново, уже с оптимизациями. Зачем так, как он это решает, и сколько бывает уровней оптимизации — расскажу в следующий раз 👉
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
В прошлый раз мы прогнали код через конвейер компилятора и остановились на IL — промежуточном переносимом коде внутри .dll 📦
Но процессор про IL ничего не знает, ему нужны его собственные инструкции. Вот тут-то JIT и делает этот последний перевод
🕐 Кто такой JIT
JIT — Just-In-Time, «точно в срок». Это ещё один компилятор, но работает он не при сборке, а уже во время выполнения программы: берёт IL и превращает его в машинный код процессора, на котором прямо сейчас выполняется программа
JIT делает это лениво, по методам. То есть, компилируется не весь .dll разом, а конкретный метод в момент своего первого вызова. Дальше уже используется его машинный код 🐁
🛠 Это компилятор, а не переводчик слов
JIT — это именно компилятор. Он не идёт по IL шаг за шагом, как интерпретатор, и не перекладывает одну инструкцию в другую один-в-один. Он подбирает инструкции текущего процессора, раскладывает значения по регистрам, кое-где оптимизирует
🔎 Смотрим своими глазами
Возьмём наш однострочник. В IL он был представлен тремя инструкциями: ldstr, call, ret. После обработки JIT-ом под x64 ЦПУ они превращаются в настоящий ассемблер (дикпик 1):
mov rdi, <адрес строки>
call Console.WriteLine(String)
ret
ldstr стал mov — кладём адрес нашей строки в регистр, откуда его заберёт вызов. call остался call, ret остался ret. А ещё JIT сам дописал пролог и эпилог (push/add вокруг) — это возня со стеком под вызов, которой в IL не было 🧹
Вот это уже те самые инструкции, которые процессор исполняет напрямую
Этот машинный код никуда не сохраняется на диск — JIT кладёт его прямо в память процесса, и с концом программы он исчезает. Почему так и чем за это платим — вернёмся, когда дойдём до памяти 🧠
🅰️ Что с этим делать
🟢 Первый вызов метода всегда чуть дороже: ровно в этот момент JIT его и компилирует. Из-за этого возникает такое явление, как «холодный старт», когда самые первые прогоны методов медленнее последующих
🟢 IL не исполняется сам по себе. Он лишь заготовка, которую JIT собирает в машинный код под запуск. Дальше уже работает этот нативный код
Причём JIT в первый раз переводит метод наспех, лишь бы программа уже начала выполняться. А если метод оказывается «горячим», то есть если он вызывается снова и снова, то в какой-то момент JIT компилирует его заново, уже с оптимизациями. Зачем так, как он это решает, и сколько бывает уровней оптимизации — расскажу в следующий раз 👉
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
В прошлый раз мы увидели, как JIT переводит IL в машинный код процессора
Там я упомянул, что обычно метод сначала компилируется «наспех», чтобы он вообще работал. А если метод окажется горячим
Разберёмся, почему так, что такое горячий метод, и что за оптимизации👇
🎯 Дилемма: быстрый старт или быстрый код
У JIT конфликт интересов: по-хорошему, надо бы оптимизировать весь код, чтобы он был очень быстрым и эффективным. Но оптимизация — дело небыстрое. Если возиться с каждым методом при первом же вызове, программа будет мучительно долго стартовать 🐢
При этом некоторые методы вызываются лишь несколько раз за время выполнения программы, так что тратить время на их оптимизацию бессмысленно. А те, что вызываются постоянно, наоборот, хочется сделать супер-быстрыми 🔥
🟢 Tier0 — быстрый перевод без оптимизаций, чтобы метод поскорее заработал
🟢 Tier1 — компиляция с полным набором оптимизаций. Применяется только к тем методам, которые вызываются достаточно часто
🧰 Какие именно оптимизации?
🟢 Встраивание: тело короткого метода подставляется прямо в место вызова. Экономим на переходе к нему и открываем дорогу другим оптимизациям
🟢 Раскладка по регистрам: нужные значения переменных, аргументов, и промежуточных вычислений держатся в регистрах процессора — самой быстрой памяти, а не гоняются в оперативную память
🟢 Разворачивание циклов: короткий цикл превращается в несколько дублирований тела цикла, чтобы реже проверять условие выхода
🟢 Выкидывание лишнего: вычисления, результат которых никто не читает, просто убираются
♨️ Как JIT понимает, что метод горячий
У каждого метода есть счётчик вызовов. Пока его значение небольшое, вызывается та версия метода, которая была скомпилирована JIT при первом вызове, то есть на уровне Tier0. Как только счётчик переваливает за определённый порог, к методу в фоне применяется Tier1, и следующие вызовы идут уже к оптимизированному методу
По умолчанию этот порог составляет 30 вызовов (
DOTNET_TC_CallCountThreshold). Это можно увидеть вжиую, если включить сводку JIT (DOTNET_JitDisasmSummary=1) и гонять метод нужное число раз:calls=30 → JIT compiled Compute [Tier0]
calls=31 → JIT compiled Compute [Tier0]
JIT compiled Compute [Tier1]
До 30 вызовов включительно метод так и остаётся скомпилированным на Tier0. На 31-м же видно, что метод скомпилировался заново, уже на Tier1
🅰️ Что запомнить
🟢 Первые вызовы горячего метода идут по версии, скомпилированной на Tier0, то есть без особых оптимизаций
🟢 Если метод вызывается достаточно часто (по умолчанию, больше 30 раз), то он компилируется на Tier1, с оптимизациями
🤯 Вот это поворот
Заглавный дикпик заспойлерил, что на самом деле уровней больше😱
Ну или не прям уровней, а разновидностей Tier0 и Tier1, но это уже разберём в следующий раз 👉
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
Please open Telegram to view this post
VIEW IN TELEGRAM
🐸 О чём этот канал?
Кажется, я уже достаточно давно веду этот канальчик, и пора бы описать его лор. Чтобы всем пришедшим было интереснее читать местные материалы, а мне не приходилось каждый раз пояснять, кто все эти люди и почему я опять что-то сломал.
Меня зовут Дима. Пишу на шарпе больше десяти лет, сейчас тимлид в команде, которая пилит внутреннюю базу знаний. Поэтому в канале много ASP.NET, EF Core, миграций на новые версии .NET и прочей будничной бэкендерской жизни.
Живу в Батуми. Отсюда посты про пет-проекты вроде Тралебота — бота для обучения грузинскому — и истории про его разработку и поддержку типа “А у меня локально не работает!”
Когда-то заводил этот канал, потому что заебался голосом всем подряд рассказывать, про новенькие приколюхи которые прочитал. А делиться очень хотелось!
Было тут всякое, но вот что мне самому нравится больше всего:
1. Серия про AI. Начиналась ещё с разбора, как вообще работает LLM под капотом потом был Semantic Kernel, а докатилось всё до топ-11 граблей на нашем MCP для базы знаний.
2. Недобитая серия про кафку. Имеется! Когда-нибудь добью, честно.
3. Стриминг вместо ToList: как отдавать большие ответы, не собирая их в памяти.
4. Rate limiting, который в ASP.NET есть из коробки, но не все про него знают.
5. Рабочая история про коллабу с Геншином
Стараюсь писать в основном про то, что попробовал в проде, или про то, что есть шанс попробовать. Всегда преследую цель, чтобы после прочтения возникло ощущение «вау, оказывается это просто, пойду сделаю». Для этого в конце некоторых статей делаю блок 🅰️ Итого с коротким советом, как воспользоваться той или иной штукой.
А ещё у нас коллаба со Стёпой @drummer_programmer Гранкиным, с которым мы долго работали вместе. У него здесь своя рубрика «Тяжёлые среды»: по средам он берёт одну тему и копает её серией, пока не докопает. Летом это была внутрянка Postgres, а сейчас идет осенний марафон «от кода до железа»: что происходит с памятью и процессором, когда ты запускаешь обычный Hello World.
Посты выходя приблизительно 1,999991 раза в неделю. Специально не чаще: не хочется устраивать любимейшим читателям ФОМО. Лучше реже, но чтобы каждый пост было не жалко открыть🥰
И да, канал 18+нахуй 🔞. Я иногда стыдливо матерюсь за спойлерами . В последнее время меньше, но видимо это просто сезон такой. Ну и шутки у меня странные — куда без этого?
Кстати, про AI, агентов и LLM в разработке у меня отдельный канал, @neuralfordevs. Если здесь вам заходят посты про MCP, там этого сильно больше. Ну если я его не заброшу...
Короче, будем знакомы. Расскажите в комментах, кто вы и на чём пишете, а то я вас тут воображаю в основном по реакциям на дикпики с кодом.
#devlife
Кажется, я уже достаточно давно веду этот канальчик, и пора бы описать его лор. Чтобы всем пришедшим было интереснее читать местные материалы, а мне не приходилось каждый раз пояснять, кто все эти люди и почему я опять что-то сломал.
Меня зовут Дима. Пишу на шарпе больше десяти лет, сейчас тимлид в команде, которая пилит внутреннюю базу знаний. Поэтому в канале много ASP.NET, EF Core, миграций на новые версии .NET и прочей будничной бэкендерской жизни.
Живу в Батуми. Отсюда посты про пет-проекты вроде Тралебота — бота для обучения грузинскому — и истории про его разработку и поддержку типа “А у меня локально не работает!”
Когда-то заводил этот канал, потому что заебался голосом всем подряд рассказывать, про новенькие приколюхи которые прочитал. А делиться очень хотелось!
Было тут всякое, но вот что мне самому нравится больше всего:
1. Серия про AI. Начиналась ещё с разбора, как вообще работает LLM под капотом потом был Semantic Kernel, а докатилось всё до топ-11 граблей на нашем MCP для базы знаний.
2. Недобитая серия про кафку. Имеется! Когда-нибудь добью, честно.
3. Стриминг вместо ToList: как отдавать большие ответы, не собирая их в памяти.
4. Rate limiting, который в ASP.NET есть из коробки, но не все про него знают.
5. Рабочая история про коллабу с Геншином
Стараюсь писать в основном про то, что попробовал в проде, или про то, что есть шанс попробовать. Всегда преследую цель, чтобы после прочтения возникло ощущение «вау, оказывается это просто, пойду сделаю». Для этого в конце некоторых статей делаю блок 🅰️ Итого с коротким советом, как воспользоваться той или иной штукой.
А ещё у нас коллаба со Стёпой @drummer_programmer Гранкиным, с которым мы долго работали вместе. У него здесь своя рубрика «Тяжёлые среды»: по средам он берёт одну тему и копает её серией, пока не докопает. Летом это была внутрянка Postgres, а сейчас идет осенний марафон «от кода до железа»: что происходит с памятью и процессором, когда ты запускаешь обычный Hello World.
Посты выходя приблизительно 1,999991 раза в неделю. Специально не чаще: не хочется устраивать любимейшим читателям ФОМО. Лучше реже, но чтобы каждый пост было не жалко открыть
И да, канал 18+
Кстати, про AI, агентов и LLM в разработке у меня отдельный канал, @neuralfordevs. Если здесь вам заходят посты про MCP, там этого сильно больше. Ну если я его не заброшу...
Короче, будем знакомы. Расскажите в комментах, кто вы и на чём пишете, а то я вас тут воображаю в основном по реакциям на дикпики с кодом.
#devlife
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
C# Short Posts 🔞
☺️ А у меня локально не работает!
Если долго меня читаете, то может слышали, что есть у меня такой пет-проектик для изучения языков — тралебот.
Я его сейчас довольно активно развиваю, ну и разумеется, пользуюсь им сам. 🐶
Проснувшись однажды утром после…
Если долго меня читаете, то может слышали, что есть у меня такой пет-проектик для изучения языков — тралебот.
Я его сейчас довольно активно развиваю, ну и разумеется, пользуюсь им сам. 🐶
Проснувшись однажды утром после…
🔥6 4❤2
C# Short Posts 🔞 pinned «🐸 О чём этот канал? Кажется, я уже достаточно давно веду этот канальчик, и пора бы описать его лор. Чтобы всем пришедшим было интереснее читать местные материалы, а мне не приходилось каждый раз пояснять, кто все эти люди и почему я опять что-то сломал. …»
В прошлом году за две недели до выступления я зачитывал свой доклад вслух минимум раз в день. В итоге отточил доклад настолько, что мог прочитать его буквально с любой минуты. То есть называешь мне минуту или слайд и я с этого места прям мог начать доклад.
Думаю, что я как-то сильно запариваюсь, но очень хочется выдать максимум тем, кто придет послушать. Так что в этом году делаю так же. Вот как раз этот пост пишу после очередного прогона
Такая подготовка хоть и выматывает, но на самом деле очень помогает, так как к выступлению рассказ настолько сидит в голове, что сбиться становится попросту невозможно — даже если что-то пойдет не так и какая-то демка застрянет, то остается еще очень много когнитивной энергии на импровизацию.
Вот такие вот перцы
А так конечно хочется сюда написать про новые приколюхи и про то, как выстрелило в итоге понимание стриминга на проде, про которое я буквально недавно тут писал. Видимо чуть позже. А то все силы и время уходят на подготовку. Так что пожелайте мне удачи и приходите послушать про MAF. Вроде получается интересно!
#devlife
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
C# Short Posts 🔞
Как стримить данные в ASP.NET и как их принять
Бахнул небольшую статейку про стриминг на хабр. Постарался чуть более подробно и просто рассказать про самые основные способы, которые есть.
➡️ Читать тут ⬅️
Немного в ней дополнил то, что было в постах…
Бахнул небольшую статейку про стриминг на хабр. Постарался чуть более подробно и просто рассказать про самые основные способы, которые есть.
➡️ Читать тут ⬅️
Немного в ней дополнил то, что было в постах…
❤7 3🔥1
🎯 Instrumented Tier0: профилирование кода
В прошлый раз мы разобрали два уровня компиляции: Tier0 — быстрый и неоптимизированный, и Tier1 — оптимизированный, для горячих методов. Но между ними прячется ещё одна стадия❗️
Возьмём наш однострочник
Один вызов — один дешёвый заход на Tier0, и программа кончилась. А теперь позовём тот же метод в цикле много раз:
🕵️ Что за Instrumented Tier0?
Это тот же неоптимизированный Tier0, но со встроенными счётчиками. Пока метод работает на нём, рантайм записывает статистику: сколько раз метод вызвали, какие ветки чаще срабатывают, какие типы приходят в вызовы. Такая статистика — это профиль работы метода, а сам сбор называется профилированием
Дальше, на стадии Tier1, JIT оптимизирует метод с учётом профиля его работы, чтобы ускорить его наиболее горячие точки, а не просто делать это наугад
Такая оптимизация называется PGO — Profile-Guided Optimization, и по умолчанию она включена с .NET8
⏱️ Почему до Tier1 теперь два этапа
В прошлом посте я говорил про порог в 30 вызовов между Tier0 и Tier1. Если бытьдушным точным, это актуально для кода, работающего на .NET < 8 или .NET>= 8, но с отключенной PGO (
Первые 30 вызовов — это обычный Tier0, без особых оптимизаций. На 31-м метод перекомпилируется в Instrumented Tier0, на котором начинает собирать профиль работы метода. Инструментированная версия работает ещё 30 вызовов, и только потом метод компилируется на Tier1 — уже с оптимизациями по профилю. Мотивация простая: чтобы оптимизировать по профилю, профиль сначала надо собрать🧠
🅰️ Что с этим делать
🟢 В релизных сборках кода начиная с .NET8 профилирование включено по умолчанию
🟢 Профилирование позволяет более точно ускорить горячий код: JIT оптимизирует его по реальному поведению
🟢 Прогрев до Tier1 стал дольше: профилю нужно накопиться
А зависит ли этот путь от каких-нибудь факторов? Конечно! Но об этом расскажу в следующем посте 👉
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
В прошлый раз мы разобрали два уровня компиляции: Tier0 — быстрый и неоптимизированный, и Tier1 — оптимизированный, для горячих методов. Но между ними прячется ещё одна стадия❗️
Возьмём наш однострочник
hello world, завернём его в метод Hello() для удобства измерений и запустим один раз. В сводке JIT будет одна строка:Program:Hello() [Tier0]
Один вызов — один дешёвый заход на Tier0, и программа кончилась. А теперь позовём тот же метод в цикле много раз:
Program:Hello() [Tier0]
Program:Hello() [Instrumented Tier0]
Program:Hello() [Tier1]
🕵️ Что за Instrumented Tier0?
Это тот же неоптимизированный Tier0, но со встроенными счётчиками. Пока метод работает на нём, рантайм записывает статистику: сколько раз метод вызвали, какие ветки чаще срабатывают, какие типы приходят в вызовы. Такая статистика — это профиль работы метода, а сам сбор называется профилированием
Дальше, на стадии Tier1, JIT оптимизирует метод с учётом профиля его работы, чтобы ускорить его наиболее горячие точки, а не просто делать это наугад
Такая оптимизация называется PGO — Profile-Guided Optimization, и по умолчанию она включена с .NET8
⏱️ Почему до Tier1 теперь два этапа
В прошлом посте я говорил про порог в 30 вызовов между Tier0 и Tier1. Если быть
DOTNET_TieredPGO=0). То есть в более свежих версиях фреймворка путь до Tier1 стал в два раза дольше из-за появления ещё одной стадии, которая длится так же примерно 30 вызовов (ну или смотря что у тебя указано в DOTNET_TC_CallCountThreshold)Первые 30 вызовов — это обычный Tier0, без особых оптимизаций. На 31-м метод перекомпилируется в Instrumented Tier0, на котором начинает собирать профиль работы метода. Инструментированная версия работает ещё 30 вызовов, и только потом метод компилируется на Tier1 — уже с оптимизациями по профилю. Мотивация простая: чтобы оптимизировать по профилю, профиль сначала надо собрать
🅰️ Что с этим делать
🟢 В релизных сборках кода начиная с .NET8 профилирование включено по умолчанию
🟢 Профилирование позволяет более точно ускорить горячий код: JIT оптимизирует его по реальному поведению
🟢 Прогрев до Tier1 стал дольше: профилю нужно накопиться
А зависит ли этот путь от каких-нибудь факторов? Конечно! Но об этом расскажу в следующем посте 👉
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
Please open Telegram to view this post
VIEW IN TELEGRAM
🛤 Путь до Tier1: от чего он зависит
В прошлый раз мы гоняли
В одной программе два метода дошли до Tier1 разными путями:
📦 Код скомпилирован заранее
Готовый код оптимизирован, но слабее, чем умеет JIT, и не знает, как метод работает именно в твоей программе. Поэтому горячий метод JIT всё равно перекомпилирует. Но откатываться ради профиля на медленный Tier0 было бы жалко, так что профиль собирают прямо на оптимизированном коде со счётчиками
Это
🔁 В методе долгий цикл
Возьмём метод, который вызывается один раз, но крутит внутри цикл заданное число итераций. Сколько будет итераций, JIT заранее не знает, он видит только, что в методе есть цикл
Допустим, вызван
Обычного
А ещё в сам цикл встроен счётчик итераций. Примерно через 10 000 итераций JIT компилирует оптимизированную версию метода, и выполнение переключается на неё прямо посреди цикла: новый код подхватывает текущие значения
Такая версия метода называется
⏩ Можно обойтись без сбора профиля?
Можно. Как я говорил в прошлый раз, с выключенным профилированием (
А если пометить наш
🅰️ Что с этим делать
🟢 Tier0 → Instrumented Tier0 → Tier1 — путь по умолчанию. Готовый R2R-код пропускает Tier0, а долгий цикл получает Tier1 прямо на ходу
🟢 ReadyToRun ускоряет старт, но горячие методы JIT всё равно доработает
🟢 AggressiveOptimization редко бывает уместен: метод с ним лишается профилирования и оптимизируется по наитию, а не по профилю использованию🧠
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
В прошлый раз мы гоняли
hello world в цикле и увидели путь горячего метода: Tier0 → Instrumented Tier0 → Tier1. Но в той же сводке JIT были и другие строки, которые я тогда не показал:Program:Hello() [Tier0]
Program:Hello() [Instrumented Tier0]
System.Console:WriteLine(System.String) [Instrumented Tier1]
Program:Hello() [Tier1]
System.Console:WriteLine(System.String) [Tier1]
В одной программе два метода дошли до Tier1 разными путями:
Hello() прошёл через Tier0 и Instrumented Tier0, а Console.WriteLine обошёлся без Tier0 и сразу попал в Instrumented Tier1 🤔 Разберёмся, от чего это зависит 👇📦 Код скомпилирован заранее
Console.WriteLine — метод из библиотек самого .NET, а они поставляются не только с IL, но и с готовым машинным кодом. Такой формат называется ReadyToRun (R2R), и при первом вызове JIT-комплияция этому методу не нужнаГотовый код оптимизирован, но слабее, чем умеет JIT, и не знает, как метод работает именно в твоей программе. Поэтому горячий метод JIT всё равно перекомпилирует. Но откатываться ради профиля на медленный Tier0 было бы жалко, так что профиль собирают прямо на оптимизированном коде со счётчиками
Это
Instrumented Tier1 — разновидность Tier1, в которой тоже собирается профиль, но уже на оптимизированном коде🔁 В методе долгий цикл
Возьмём метод, который вызывается один раз, но крутит внутри цикл заданное число итераций. Сколько будет итераций, JIT заранее не знает, он видит только, что в методе есть цикл
static long LoopOnce(int n)
{
long t = 0;
for (int i = 0; i < n; i++) t += i;
return t;
}
Допустим, вызван
LoopOnce(1_000_000_000). На счётчике вызовов метода тут будет единица (сам метод же вызван один раз), однако тело цикла выполнится миллиард раз. И даже если метод потом будут вызывать снова и он дорастёт до Tier1, оптимизированный код достанется только новым вызовам, а этот миллиард итераций отработал бы на медленном коде. Но в сводке видно другое:Program:LoopOnce(int) [Instrumented Tier0]
Program:LoopOnce(int) [Tier1-OSR @0x10]
Обычного
Tier0 тут нет, потому что метод с циклом может разогреться из-за самого цикла, поэтому профиль для него начинает собираться с первого же вызова метода (Instrumented Tier0)А ещё в сам цикл встроен счётчик итераций. Примерно через 10 000 итераций JIT компилирует оптимизированную версию метода, и выполнение переключается на неё прямо посреди цикла: новый код подхватывает текущие значения
i и t и продолжает с того же места. Это место видно и в сводке: @0x10 — позиция в IL, где проверяется условие i < n 🦘Такая версия метода называется
Tier1-OSR — другая разновидность Tier1. OSR означает On-Stack Replacement, «замена на стеке»: код метода подменяется, пока его вызов ещё лежит на стеке, то есть прямо во время выполнения 🤯⏩ Можно обойтись без сбора профиля?
Можно. Как я говорил в прошлый раз, с выключенным профилированием (
DOTNET_TieredPGO=0) метод идёт напрямую Tier0 → Tier1А если пометить наш
Hello() атрибутом [MethodImpl(MethodImplOptions.AggressiveOptimization)], пропадёт и Tier0. JIT сразу скомпилирует метод со всеми оптимизациями, и в сводке останется одна строка:Program:Hello() [FullOpts]
FullOpts — это полная оптимизация вне всяких уровней, но по догадкам из вида кода, а не по реальному поведению 🤷🅰️ Что с этим делать
🟢 Tier0 → Instrumented Tier0 → Tier1 — путь по умолчанию. Готовый R2R-код пропускает Tier0, а долгий цикл получает Tier1 прямо на ходу
🟢 ReadyToRun ускоряет старт, но горячие методы JIT всё равно доработает
🟢 AggressiveOptimization редко бывает уместен: метод с ним лишается профилирования и оптимизируется по наитию, а не по профилю использованию
🧑💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
Please open Telegram to view this post
VIEW IN TELEGRAM