TGViewer
Channel Public Channel
C# Short Posts 🔞

C# Short Posts 🔞

@dimasshortposts

Здесь я, Дима Афонченко @Undermove1, публикую короткие заметки о разработке (и около). Я не претендую на правильность высказываний и открыт к дискуссиям, исправлениям и конструктивной критике. С любыми деструктивными вещами можно приходить в комменты)
Subscribers
306
Photos
206
Videos
7
Links
211
Recent Posts 13 shown
Post #501 108
🧵 Тредик для вопросов по докладу про MAF на дотнексте

В докладе многие подробности опустил, в угоду темпу и направленности мысли. Тут же можно развернуться на полную!

Буду рад любым вопросам-вопросикам!
  • ❤‍🔥 5
  • 🔥 4
  • 💘 1
Post #500 249
Даже самым хардкорным ребятам надо отдыхать, так что отдыхаем, мои чюваки 🕺
🧑‍💻dp🥁
#heavywednesday
  • 🫡 3
Post #499 507
🎯 Instrumented Tier0: профилирование кода

В прошлый раз мы разобрали два уровня компиляции: 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. Если быть душным точным, это актуально для кода, работающего на .NET < 8 или .NET>= 8, но с отключенной PGO (DOTNET_TieredPGO=0). То есть в более свежих версиях фреймворка путь до Tier1 стал в два раза дольше из-за появления ещё одной стадии, которая длится так же примерно 30 вызовов (ну или смотря что у тебя указано в DOTNET_TC_CallCountThreshold)

Первые 30 вызовов — это обычный Tier0, без особых оптимизаций. На 31-м метод перекомпилируется в Instrumented Tier0, на котором начинает собирать профиль работы метода. Инструментированная версия работает ещё 30 вызовов, и только потом метод компилируется на Tier1 — уже с оптимизациями по профилю. Мотивация простая: чтобы оптимизировать по профилю, профиль сначала надо собрать🧠

🅰️ Что с этим делать

🟢 В релизных сборках кода начиная с .NET8 профилирование включено по умолчанию
🟢 Профилирование позволяет более точно ускорить горячий код: JIT оптимизирует его по реальному поведению
🟢 Прогрев до Tier1 стал дольше: профилю нужно накопиться

А зависит ли этот путь от каких-нибудь факторов? Конечно! Но об этом расскажу в следующем посте 👉

🧑‍💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
  • ❤ 3
  • 🔥 2
  • 🤔 1
  • 🤩 1
Post #498 333
🔜 Готовлюсь к DOTNEXT 2026

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

Думаю, что я как-то сильно запариваюсь, но очень хочется выдать максимум тем, кто придет послушать. Так что в этом году делаю так же. Вот как раз этот пост пишу после очередного прогона 🙁

Такая подготовка хоть и выматывает, но на самом деле очень помогает, так как к выступлению рассказ настолько сидит в голове, что сбиться становится попросту невозможно — даже если что-то пойдет не так и какая-то демка застрянет, то остается еще очень много когнитивной энергии на импровизацию.

Вот такие вот перцы 😏

А так конечно хочется сюда написать про новые приколюхи и про то, как выстрелило в итоге понимание стриминга на проде, про которое я буквально недавно тут писал. Видимо чуть позже. А то все силы и время уходят на подготовку. Так что пожелайте мне удачи и приходите послушать про MAF. Вроде получается интересно!

#devlife
Telegram C# Short Posts 🔞 Как стримить данные в ASP.NET и как их принять Бахнул небольшую статейку про стриминг на хабр. Постарался чуть более подробно и просто рассказать про самые основные способы, которые есть. ➡️ Читать тут ⬅️ Немного в ней дополнил то, что было в постах…
  • ❤ 6
  • 🔥 1
Post #497
C# Short Posts 🔞 pinned «🐸 О чём этот канал? Кажется, я уже достаточно давно веду этот канальчик, и пора бы описать его лор. Чтобы всем пришедшим было интереснее читать местные материалы, а мне не приходилось каждый раз пояснять, кто все эти люди и почему я опять что-то сломал. …»
Post #496 422
🐸 О чём этот канал?

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

Меня зовут Дима. Пишу на шарпе больше десяти лет, сейчас тимлид в команде, которая пилит внутреннюю базу знаний. Поэтому в канале много 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
Telegram C# Short Posts 🔞 ☺️ А у меня локально не работает! Если долго меня читаете, то может слышали, что есть у меня такой пет-проектик для изучения языков — тралебот. Я его сейчас довольно активно развиваю, ну и разумеется, пользуюсь им сам. 🐶 Проснувшись однажды утром после…
  • 🔥 6
  • ❤ 2
Post #495 544
😎Tiered compilation: почему JIT компилирует метод минимум дважды

В прошлый раз мы увидели, как JIT переводит IL в машинный код процессора🤩

Там я упомянул, что обычно метод сначала компилируется «наспех», чтобы он вообще работал. А если метод окажется горячим🌚 , то позже он компилируется заново, уже с оптимизациями📈

Разберёмся, почему так, что такое горячий метод, и что за оптимизации👇

🎯 Дилемма: быстрый старт или быстрый код

У JIT конфликт интересов: по-хорошему, надо бы оптимизировать весь код, чтобы он был очень быстрым и эффективным. Но оптимизация — дело небыстрое. Если возиться с каждым методом при первом же вызове, программа будет мучительно долго стартовать 🐢

При этом некоторые методы вызываются лишь несколько раз за время выполнения программы, так что тратить время на их оптимизацию бессмысленно. А те, что вызываются постоянно, наоборот, хочется сделать супер-быстрыми 🔥

2️⃣ Решение — два уровня компиляции

🟢 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
  • ❤ 1
Post #493 588
⚡️ JIT: из IL в инструкции процессора

В прошлый раз мы прогнали код через конвейер компилятора и остановились на 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
  • ⚡ 1
  • 👍 1
  • 🔥 1
Post #490 613
🧩 Компилятор: что он делает с твоим кодом

В прошлый раз мы разрезали однострочник "Hello, World!" как торт и увидели его слои, от компилятора до процессора 🍰
Давай разберёмся с верхним слоем — компилятором

⚙️ Что это вообще такое

Компилятор — переводчик. Он берёт код, который пишет и понимает человек, и переводит его в форму, которую способна понимать и исполнять машина, а человек там мало что поймёт (да ему и не надо). Отдельно качать компилятор обычно не надо: он приезжает вместе с установкой .NET SDK (набор инструментов для разработки) и работает на этапе сборки, ещё до того, как программа запустится🧩

Кажется, что такой перевод — это очень просто, "просто одну строку сконвертировать в другую, и всё!"👩‍💼 На самом деле это целый завод с конвейером: несколько фаз, и каждая передаёт результат следующей 🏭

Давай посмотрим, что рождается на выходе каждой🔎


🔤 Фаза 1. Разбор на токены (лексер)

Текст рубится на токены — минимальные значимые кусочки🪓
Результат — плоский список:

Console · . · WriteLine · ( · "Hello, World!" · ) · ;

Пока это просто «слова», без понимания, что они значат.

🌳 Фаза 2. Сборка дерева (парсер)

Токены складываются в синтаксическое дерево — оно отражает грамматику: что во что вложено. Упрощённо это выглядит так:

вызов метода
 ├ что зовём: Console.WriteLine
 └ аргумент: "Hello, World!"

Полное дерево, прямо из компилятора — на дикпике 1. Смысла в нём всё ещё нет, это чистая форма

🧠 Фаза 3. Добавление смысла (семантика)

Компилятор проходит по дереву и добавляет узлам смысл — формализует, какой символ и тип за ними стоит. Результат — на дикпике 2. Если при написании кода ошибёшься в имени или типе, то компилятор будет ругаться на это именно на этом этапе

📦 Фаза 4. Генерация (emit)

В конце дерево со смыслом превращается в IL (Intermediate Language, промежуточный переносимый код, но это ещё не машинный код🐁)
Результатом компиляции одной нашей строки станут три инструкции:

ldstr  "Hello, World!"
call   Console.WriteLine(string)
ret

Они пакуются в сборку — файл .dll

🎩 А теперь фокус

В дереве не было ни класса, ни метода — мы их не писали. Но C# годами требовал каркас: class Program со статическим методом Main внутри.

Если заглянуть в собранный .dll, внутри окажется Program, а в нём метод <Main>$(string[]).

Компилятор дописал каркас за нас, а наш однострочник стал телом этого метода. Причём имя метода специально сделано таким сложным, чтобы к нему нельзя было обратиться из кода🛑

🅰️ Что с этим делать

🟢 Ошибка компиляции может возникнуть на одном из этапов сборки: не закрылась скобка — это парсер, опечатка в имени типа — это семантика
🟢 В результате компиляции получается .dll с IL внутри.

Как IL превратится в настоящие инструкции процессора — узнаем в следующий раз👉

🧑‍💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
  • 👍 4
  • ❤ 1
Post #488 604
🚀 Новая серия: от кода до железа

Когда в мою жизни пришла #ИИшница, я понял, что с ней можно очень глубоко погрузиться в любую тему: как в устройство баз данных, так и в устройство программ😣

Мне стало интересно разложить прям по байтикам максимально простой однострочный "Hello world!"🖥

Оказалось, что он как торт 🎂 (или как лук🧅, или как огр 🤥), состоит из нескольких слоёв 🔥
Между строкой, которую ты пишешь, и транзистором, который её исполняет, ещё целая стопка: компилятор, рантайм, операционная система, и процессор. Давай вместе пройдём через все эти слои сверху вниз🔽

Начнём с самого начала. Вот файл hello.cs:
Console.WriteLine("Hello, World!");

Вот команда запуска:
dotnet run hello.cs

⚙️ Что происходит под капотом
Код на C# не исполняется напрямую на процессоре. Сначала компилятор (его зовут Roslyn) переводит наш код в промежуточный байт-код — IL (Intermediate Language). Это ещё не инструкции процессора, а переносимый «полуфабрикат».
Потом стартует рантайм — CoreCLR, движок .NET. Он берёт IL и уже на лету, прямо во время работы, до-компилирует его в настоящие инструкции процессора. Это называется JIT (Just-In-Time): метод компилируется в момент первого вызова.

Важная деталь про масштаб: IL-код нашей строки весит 11 байт, а рантайм, который его исполняет, весит 77 МБ. То есть наш с тобой код — это тонкая надстройка над огромным механизмом, который работает задолго до первой инструкции нашего кода и делает всё необходимое, чтобы мы увидели заветные строки на экране⚙️

Что именно он делает?
Создаёт потоки
Поток (thread) — это отдельная линия исполнения внутри процесса нашей программу, которую операционная система раскладывает по ядрам процессора. В одном процессе их может быть достаточно много.

Сколько?

Правильный ответ, конечно, «зависит». При отладке их можно получить целых 7! 7️⃣
И число это не фиксированное, его можно регулировать 🔧

А что ещё?
Занимает память 🫡
Физически процесс держит около 24 МБ, но нашей программы там почти нет: больше 20 МБ — это сам рантайм (движок CLR, JIT-компилятор, библиотека базовых типов). Из нашего там два места:

🔲стеки потоков: у каждого из семи потоков свой стек под локальные переменные и кадры вызовов функций. Под него резервируется несколько мегабайт адресов, но физически занято по паре килобайт на поток

🕳 GC-куча (heap): здесь живут объекты, созданные через new. За этим простым словом скрывается целый мир со своими регионами:
👶gen0/👨‍🦱gen1/👴gen2 — здесь хранятся разные поколения «обычных» объектов (размером до 85 000 байт)
🌾 LOH — большие объекты (от 85 000 байт)
📍 POH — закреплённые (pinned) объекты
🧊 FOH — литералы и «вмороженные» объекты

И это далеко не всё!

🅰️ Что с этим делать
🟢 Программы работают на целой стопке слоёв: от кода и компилятора до операционной системы и процессора
🟢 За классическим «Hello world» стоит огромный механизм — рантайм, который делает всё необходимое, что наша программа сработала в том окружении, в котором оказалась

Если тебе что-то не понятно из вышесказанного👆 — не страшно, мне тоже многое не понятно, так что будем разбираться вместе🫂
Постепенно углубимся в каждую тему и разберёмся, как это всё работает💪

🧑‍💻dp🥁
#dotnet #csharp #инженерныештучки #heavywednesday
  • ❤ 5
  • 🌚 1
Post #487 424
Older posts →

About this channel

How can I read @dimasshortposts without a Telegram account?
TGViewer shows the public web preview Telegram publishes for C# Short Posts 🔞: recent posts, photos, videos and the subscriber count, with no app, login or account.
How many subscribers does C# Short Posts 🔞 have?
C# Short Posts 🔞 (@dimasshortposts) has 306 subscribers on Telegram, refreshed roughly every 30 minutes.
Does C# Short Posts 🔞 know I viewed it here?
No. Public channel previews carry no viewer identity, and TGViewer has no accounts or tracking of what you look up.
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →