.NET, что с тобой происходило в 2021?

Конец декабря, многие подводят свои итоги, а мы решили оглянуться и посмотреть, что хорошего (и не очень) случилось в мире .NET-разработки за этот год, и спросили об этом наших разработчиков.

Из хорошего называли появление рекордов в С# 9, и выход С# 10, а в топе, конечно же, релиз .NET 6 с долгосрочной поддержкой. До выхода шестой версии у нас уже много веб-сервисов работало на .NET 5, в том числе и в монолите. Теперь ко всем «фишкам» пятой версии добавляются преимущества новой.

Платформа становится всё кросплатформеннее и кросплатформеннее (возможно, благодаря этому в последнее время наблюдается рост интереса к C#). В .NET 6 сильно улучшилась производительность, появилась поддержка Apple Silicon (мы недавно писали, почему так рады этому), новые API и много других полезностей.

AspNet.Core вышел в топ веб-фреймворков по производительности и обогнал Java в рейтинге TechEmpower по облачным платформам. Что за этим стоит?

Работа с памятью

Есть ряд улучшений, работающих с представлением объектов в памяти:

  • Span позволяет выполнять меньше аллокаций и избежать копирования памяти в некоторых случаях;

  • Pipelines позволяет уменьшить копирование данных между стадиями обработки  HTTP-запросов;

  • много где выделение новых объектов заменили на пул объектов: заранее заготовленные и/или ранее выделенные объекты используются многократно.

JIT-компилятор

Производительность улучшается за счёт очень большой работы в области JIT.

Огромное количество улучшений сделали в части intrinsics, SIMD, AVX. Это позволяет JIT-компилятору заменять общие для всей архитектуры инструкции процессора на конкретные в зависимости от железа, на котором работает программа. В некоторых случаях целые сложные процедуры заменяются на всего лишь одну инструкцию класса SIMD, например AVX.

Эти наборы инструкций основаны на принципе Single Instruction — Multiple Data. Обычно эти инструкции применялись в обработке звука, изображения и в трёхмерной графике, где за одну операцию можно обработать одинаковым способом сразу несколько чисел, представляющих из себя табличное представление звуковой волны, пикселов на экране или компонент векторов. Но точно так же эти инструкции, оказывается, годятся для обработки текста, например JSON, поскольку текст тоже представлен в виде набора чисел, и теперь мы можем за одну операцию обработать сразу 16 букв, если речь идёт об AVX256.

Profile-guided optimization (PGO)

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

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

Devirtualization

Применяются алгоритмы, которые позволяют собрать статистику выполнения и сделать вывод о том, что каким-то часто используемым методам нужно предоставить зелёный свет и позволить им миновать таблицу виртуальных функций при вызове. Это уменьшает количество прыжков до кода функции.

Inline

Inlining — это когда мы заменяем невиртуальный или девиртуализированный вызов метода на инструкции процессора, находившиеся в методе. Разместив инструкции рядом и устранив лишние переходы, мы добиваемся лучшей cache locality, а также позволяем процессору воспользоваться собственными оптимизациями. Похоже на то, как цитата вставляется прямо в текст, чтобы не открывать его по ссылке отдельно. Эти две технологии вместе дают процессору те же преимущества, которые есть у читателя, у которого цитата перед глазами: можно быстрее прочитать и понять.

Сами по себе эти технологии существуют давно, но весь вопрос в том, в какой степени и как качественно они используются сейчас для .NET. Что конкретно повлияло на позиции C# в рейтинге — мне неизвестно. Скорее всего, это сочетание многих факторов. Сейчас код на C# конкурирует по производительности с заранее скомпилированным кодом на языках C++, Rust, Go.

Эти улучшения — не просто строчки в рейтинге для команды .NET, а то, что позволит  нам сэкономить вполне ощутимые ресурсы облачной инфраструктуры, и самое ожидаемое именно в NET 6.0 — это технология PGO. Думаю, что за счёт этой технологии asp.net продвинется ещё выше по рейтингу в следующем раунде, а нам позволит сэкономить ещё больше на облачных ресурсах.

— Какое самое классное, на твой взгляд, изменение случилось в мире .NET-разработки в 2021?

Изменение, которое для меня наиболее значительно, произошло не в 2021, а в конце 2020: выход .NET 5. Эта версия важна тем, что в ней слились две ветки развития платформы. Начиная с пятёрки, больше не будет Core и полного фреймворка. Теперь только .NET и всё. Кроме того, с выходом пятой версии отпала необходимость в .NET standard — новые версии больше не будут выпускаться. Скажем старичку спасибо: он сделал очень большую работу для прекрасного «дотнета будущего».

– Что не изменилось, а хотелось бы?

Здесь не буду оригинален. Мне, как и многим, не хватает алгебры над типами и в частности Discriminated union. DU Proposal висит в статусе чемпиона аж с 2017-го года, но начать работу по нему мешало отсутствие Record type. В C# 9 рекорды наконец-то вышли и теперь можно ожидать, что в ближайших версиях C# мы увидим DU.

— Твой личный список топ-5 изменений в .NET в 2021.

1. Visual Studio 2022

«Вот тебе 64Гб RAM, кушай» хотел бы я сказать, но 32-битное адресное пространство смотрело на меня с троллфейсом, а очередное падение devenv.exe с OOM вызывало приступ гомерического смеха. И вот мы дождались. Наконец-то Visual Studio — 64-битный процесс. И это, на мой взгляд, самое важное изменение в этом мажорном релизе.

2. C# 10 и Minimal API

Global и implicit using позволили упростить Hello, World! до одной строки. Минимальный объём кода бэкенд-приложения на ASP.NET Core сократился до одного файла на четыре строчки. В глазах новичков .NET в целом (и C# в частности) стал немного дружелюбнее – и это хорошо. File-scoped namespaces позволяют избавиться от одного уровня вложенности скобочек в 99% случаев. В сочетании с сократившейся портянкой using-ов код читается немного легче. Довольно приятные изменения, как по мне.

3. M1 и .NET 6

.NET 6 получил нативную поддержку ARM64, в том числе и под macOS. Интерес к ARM-архитектуре год от года растёт, Graviton — уже не шутка, и улучшение поддержки этой архитектуры несомненно радует.

4. Ahead of Time компиляция в WebAssembly.

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

Первая реализация предлагала компиляцию в WASM только кода самой исполняющей среды .NET, а всё, что было написано вами – публиковалось в виде .dll с байт-кодом, который интерпретировался исполняющей средой. С релизом .NET 6 появилась поддержка полной AoT компиляции всего кода приложения, включая тот, что написан вами. Как итог — весьма интересная технология.

5. Profile guided optimization (PGO)

Profile-driving inlining, guarded devirtualization, hot-cold block reordering, loop clonning, inlined casts и много чего ещё. Как в динамическом, так и в статическом виде, с возможностью собрать профиль оптимизации с работающего приложения в продакшене. Это возможность получить ощутимую дополнительную производительность приложения ценой немного увеличенного времени запуска. Например, для тестового сценария TechEmpower JSON MVC, производительность поднялась с 510k RPS до 640k RPS. Keep calm and set DOTNET_TieredPGO=1

— Что не изменилось, а хотелось бы?

Отношение людей к .NET. Для многих это всё ещё монстр, созданный злым и страшным Microsoft, чтобы в злом и страшном Windows клепать злые и страшные формочки для злого и страшного Sharepoint. Но в то же время любой может взять машину под управлением Linux, Windows или macOS на x86 или ARM, написать приложение, запустить его локально или задеплоить в bare-metal Linux или в k8s. Где тут Windows или Sharepoint? Вопрос риторический. По факту .NET — давно не Windows-only как для разработки, так и для эксплуатации. Но люди живут стереотипами, и это печалит.

Андрей Парамонов

Twitter

Я ждал ровно одну маленькую фичу — Null Parameter Checking, но её так и не завезли. Мелочь на самом деле, а приятно, что код не надо писать больше самому.

Hot Reload

В .NET 6 обещали Hot Reload (обновление приложения прямо в моменте написания кода, без перезагрузки). С функцией случился интересный кейс: Microsoft сначала включила эту функцию в открытый код .NET 6, а потом решила убрать. Но сообщество подняло бунт, и Hot Reload вернули в открытый код.

MAUI

Летом появился кроссплатформенный фреймворк MAUI для создания приложений от Microsoft. Хочу найти время, чтобы попробовать его в действии. С MAUI одна большая прелесть: написал приложение на C# и можешь адаптировать его к любой платформе (Linux, Windows, Android, OS X).

Нативное выделение памяти

Настораживает введение NativeMemory в .NET 6, т.е. возможность делать знаменитые alloc и free операции для всяких оптимизаций. C# хотят превратить в C? Не знаю, хорошо это или плохо, смотря как использовать. Да, это добавляет больше гибкости и универсальности платформе, но и автоматическое управление памятью придумали не просто так — оно закрывает целый ряд кейсов. В общем, надо очень хорошо понимать, зачем ты собираешься это использовать.

Вместо заключения

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

Читайте так же: