.NET становится по-настоящему кроссплатформенным: после долгого ожидания наконец объявлена дата релиза ASP.NET Core, JetBrains готовит альтернативу Visual Studio на базе ReSharper и IDEA, Microsoft приобрела Xamarin, сделала Xamarin Community бесплатной, а Mono перевела на MIT-лицензию и наконец, Windows Server 2016 получит поддержку Windows-контейнеров в Docker.
С новыми возможностями нас встречают новые вызовы:
- Как будет работать один и тот-же код под .NET Core и Mono, на Windows и Linux, в docker-контейнере?
- Стоит ли переходить на .NET Core уже сейчас и как получить максимум от новой платформы?
- Какие перспективы у Mono и Xamarin?
- Какие изменения произошли «под капотом» .NET с переходом на Roslyn и .NET Core?
Всего через три недели на конференции DotNext в Питере 20 спикеров выступят с докладами о настоящем и будущем платформы .NET, об оптимизации производительности и многопоточности, о внутреннем устройстве платформы .NET и CLR, о профилировании и отладке .NET-кода.
А пока мы попросили четырех из них поделиться своим опытом и мнениями о грядущих изменениях в мире .NET. На наши вопросы ответили:
- Ведущий мировой эксперт по производительности .NET-платформы, восьмикратный Microsoft MVP, автор прекрасной книги по производительности .NET «Pro .NET Performance» Саша Голдштейн;
- Главный разработчик протокола реактивного многопроцессного взаимодействия в Rider Дмитрий Иванов из JetBrains;
- Microsoft MVP, к.ф.-м.н., серебряный призёр ACM ICPC, постдок в Вейцмановском институте науки и разработчик JetBrains Андрей Акиньшин;
- CTO Promarket и эксперт в области Mono и Linux Никита Цуканов.
Что изменилось в .NET с переходом на Roslyn?
Саша Гольдштейн
Для Майкрософт Roslyn – проект огромной важности. Почти все в C#-команде работали над ним лет семь, наверное, и очень долго не могли выпустить. И сейчас он на github в open-source и весь процесс изменений языка перед глазами. С одной стороны, появилось много людей со странными идеями добавить в C# конструкции из других языков, с другой – весь процесс происходит в открытую и за два часа на любой вопрос тебе могут ответить три человека, которые пишут компилятор. Для community – это очень здорово.
Андрей Акиньшин
В целом Roslyn мне очень нравится. Как компилятор он лучше, чем старый: многие участки кода он компилирует более грамотно и более удачно с точки зрения производительности. Что касается RyJIT, однозначно ответить нельзя. Одной из основных задач RyuJIT было снижение время JIT-компиляции. Мы даём пользователю очень быстрый старт, но из-за этого не можем сделать клёвые оптимизации, поэтому сам код работает медленнее, чем мог бы, а иногда даже медленнее, чем старый JIT. При этом RyuJIT умеет использовать SSE, AVX-инструкции, используются более современные операции над числами с плавающей точкой.
В плане производительности, мне кажется, Microsoft делает ставку на другую технологию, которую сейчас активно развивает: .NET Native. Пока это все еще в очень сыром состоянии, но выглядит многообещающе.
Никита Цуканов
Главная фишка Roslyn в том, что Microsoft могут теперь с приемлемой скоростью добавлять новые фичи. Раньше был один компилятор для сборки, другой – для студии. Теперь этого нет – остался один компилятор и он написан на C#.
Дмитрий Иванов
С точки зрения API конечно стало лучше, но есть проблемы с производительностью и лишней памятью, и они далеки от решения. Теперь мы не можем в Visual Studio 2015 вместе с ReSharper открывать гигантские проекты, потому что не хватает памяти. Кроме этого нельзя выключить анализатор Roslyn.
Можете сравнить Mono с .NET Core? Каковы перспективы того и другого?
Саша Гольдштейн
Mono переходит на MIT-лицензию. Значит, Microsoft смогут скопировать куски Mono в CoreFx и пользоваться ими. Мне кажется, что Mono потихоньку должен угаснуть. У Mono будут какие-то ниши, на которых .NET Core пока не будет работать. Но сам runtime у Mono так себе: GC только недавно перешёл на Generational GC, JIT – работает не очень. И сам фреймворк, который они написали во многих случаях не production-ready: там куча багов, баги обнаруживаются и подолгу не чинятся. Почти все мои клиенты, использовавшие Mono в production, сталкивались с проблемами. Это не значит, что в итоге система совсем не работала, но это не тот level of quality, к которому мы привыкли в .NET.
Но с .NET Core тоже не всё гладко. Проект должен был закончиться в январе, потом в марте, буквально несколько дней назад, сказали, что в конце июня будет готово. API, проектная модель, половина кода постоянно меняются.
На прошлой неделе один из клиентов с проектом на ASP.NET MVC 6 спросил, как я считаю, следует ли чинить код каждые две недели или лучше сделать backport на ASP.NET MVC 5 и подождать. Вопрос не простой.
Андрей Акиньшин
.NET Core – безусловно очень интересный проект, большой шаг вперед, но на сегодняшний день он ещё сырой. Не будем забывать, что многие библиотеки под него еще не вышли или находятся в RC. Плюс Microsoft постоянно что-то переименовывает, каждый день все меняется, одни баги чинятся, другие добавляются.
Mono разрабатывается более 15 лет. Это взрослый, солидный рантайм, на котором работает много production-кода. Я в JetBrains работаю над проектом Rider (кроссплатформенная C# IDE). Backend Rider’а (это в чистом виде ReSharper) работает под Mono, а под .NET Core пока не очень получается запустить. И отлично работает: огромное приложение из сотен проектов и очень нетривиальной логикой, со всеми фичами .NET, и всё это под *nix нормально заводится. А под .NET Core не заводится, так что, нужно ждать, пока .NET Core станет более стабильным.
Никита Цуканов
Mono – достаточно зрелая среда, а .NET Core ещё в бете и по количеству доступных библиотек .NET Core безусловно проигрывает. Например, в .NET Core сейчас нет поддержки System.Drawing, а в Mono она появилась в 2000-лохматом году.
Что касается производительности и качества покрытия .NET Framework, Mono активно переносит к себе те куски, которые Microsoft открывает. Учитывая, что Microsoft приобрёл Xamarin, процесс должен пойти ещё быстрее. В какой-то момент Mono станет надстройкой над .NET Core, но пока имеет смысл использовать именно Mono.
Дмитрий Иванов
У нас, наверное, почти у всех есть мнение, что Mono в какой-то момент дропнут. В свое время Oracle тоже говорил, что объединит HotSpot JVM с JRockit, но в итоге дропнул последнюю.
Так что, наверное, стоит немного подождать, и переходить с Mono на .NET Core. Мы в Rider много работаем с Mono и видим, что там много ошибок и код сильно менее качественный, чем в .NET Framework.
Часто ли приходится придумать что-то своё и уникальное в области высокопроизводительных решений или есть хорошие готовые библиотеки?
Андрей Акиньшин
Я считаю, нет серебряной пули, нужно подбирать решение под свою задачу. Если вы работаете с БД, есть базы данных, которые работают быстрее других на определенных показателях, если вы работаете с графикой, есть хорошие библиотеки для рендеринга 2D, 3D. Нужно помнить, что каждый проект по-своему уникален. Чаще всего для одних задач подходят одни решения, для других – другие.
Очень популярная ошибка: человек слышал, что библиотека «ABC» хорошая, он её берёт и использует, не задумываясь о том, для каких задач она предназначена, на каких сценариях она ведёт себя быстро…
Важный момент, многие заморачиваются на performance и тратят много времени, чтобы получить прирост по скорости, который никто не заметит. Когда мы упираем в performance, нужно проводить исследование, делать бенчмарки, профилировать.
Дмитрий Иванов
У нас даже в рамках одной .NET-команды есть несколько решений для межпроцессного взаимодействия, поэтому да, приходится часто. Сейчас под высокопроизводительными решениями обычно понимается какая-то серверная нагрузка. Таким мы не занимаемся. JetBrains успешен именно в плане клиентских решений.
Если говорить о проблемах производительности на одной машине, она тоже постоянно решается, потому что, скажем так, мы довольно быстро выходим за рамки идиоматичного C#-кода. Довольно быстро приходится переходить с большого количества ссылок на массивы, потому уходить в unsafe. Без этого просто не работает. И наш протокол в Rider Framework мы делаем с оглядкой на высокую производительность.
В чём главная сложность многопоточного программирования на .NET? В чем главный вызов?
Саша Гольдштейн
Многим разработчиком сложно представлять, что именно происходит в многопоточной программе, что доступы к памяти могут перемещаться, что нужно заниматься синхронизацией, с другой стороны, если синхронизации много, то появляются bottlenecks. Для этого есть много инструментов, но их нужно знать и уметь пользоваться.
У многих разработчиков нет доступа к многопроцессорным системам. Когда один и тот-же код выполняется не на Core i7, а на сервере хотя-бы с 64 процессорами появляются совершенно другие проблемы. Какой-то маленький lock, который занимал 2% времени вдруг начинает занимать 50%, есть проблемы memory bandwidth (система памяти не способна отвечать на запросы достаточно быстро), не эффективное использования кеша. .NET-разработчики чаще всего даже не сталкиваются с такими задачами.
Андрей Акиньшин
Я думаю, что сложности многопоточного программирования на .NET такие-же, как на другой платформе. Нужно хорошо понимать, как работает многопоточный мир. Что касается примитив синхронизаций и concurrent-структур данных в .NET с этим все хорошо. Плюс в C# 5 появились async/await, которые позволяют писать многопоточный код в красивой стилистике.
Никита Цуканов
Нет возможности нормально предотвратить возможность записи одним потоком данных, которые находятся во владении другого. В Rust’е для этого сделали более-менее нормальную систему (уничтожающее присвоение). В .NET такого нет и без этого очень тяжко: может получиться так, что, когда мы из одного потока в другой что-то передаём, в силу невнимательности или иных причин, в другой поток может пройти что-то лишнее. И с «этим лишним» могут начать работать, хотя поток-владелец об этом ничего не знает. В этом случае конкурентные обращения на запись происходят там, где никто не знает. С ростом проекта отслеживать и бороться с этим становится все сложнее и сложнее.
Дмитрий Иванов
Хороший вопрос. Столько лет программирую многопоточно и рассказываю людям как это делать, но так сам и не разобрался. Наверное, главный challenge возникает на верхнем уровне – придумать модель, которая успешно подходит для большой команды разработчиков и большого продукта и заставить всех ей следовать и надеяться, что ты никогда не упрёшься в ограничения этой модели.
Команда Roslyn решила использовать транзакционную модель многопоточного взаимодействия, и они уперлись в Memory Wall. Мы с пессимистичными блокировками и read/write lock-моделью сталкиваемся с долгим не отпусканием read lock и не гладким тайпингом.
Серебряной пули нет, не спасают даже новомодные акторы: на них хорошо работает Hello World, но в реальности все сложнее.
Нужно ли понимать низкоуровневые детали для того, чтобы писать высокопроизводительный код или современные фреймворки избавляют от этой необходимости?
Саша Гольдштейн
Не избавляют, нужно. Вопрос только, насколько нужно. Обязательно ли уметь читать ассемблер, понимать memory model, как устроен процессор? Стандартный совет – понимайте, как минимум на один уровень ниже того, на котором вы работаете. Если вы работаете на .NET вы должны понимать, как работает ОС, Runtime, GC, Framework. Без этого, даже если вы найдёте проблему, вы не будете знать, как её исправить.
Андрей Акиньшин
Зависит от того, какие задачи решает программист. 90% времени, пока нет задачи выжимать из машины максимум, я должен думать, чтобы код был надёжным, читаемым, без багов. В 10% случаев, когда возникли проблемы или мы знаем, что проблемы возникнут, и мы стараемся их решить, да нужно знать, как все происходит внутри.
Многие люди пытаются решать performance-проблемы без этого понимания: делают неправильные бенчмарки, профилирование, пишут код, который только всё замедляет и тратят на это много времени. Чтобы справиться с 10% случаев, нужно много знать про то, как всё устроено.
Никита Цуканов
Проблема абстракций в том, что они имеют свойство протекать, поэтому в команде нужен один человек, который понимает, как внутри всё работает. Примерно также с человеком, который знает математику хорошо. Традиционные низкоуровневые проблемы – это утечки памяти, исключения в native-коде, heap corruption.
Дмитрий Иванов
.NET-инженер, который хочет разбираться в performance, должен представлять, как работает процессор и процессорные кеши, чуть-чуть представлять, как работает branch prediction и понимать, почему последовательный доступ к массиву и диску всегда работает быстрее параллельного.
Что делать, если «тормозит»? Каковы первые 2-3 шага?
Саша Гольдштейн
Не надо гадать. Нужно запустить tool и получить информацию о том, что происходит в процессе. Посмотрите память, CPU, сколько потоков, что делают. Сейчас много бесплатных утилит. После этого есть куча методов. Мой любимый – USE (utilization, saturation, errors). Его придумал Брэндон Грег (Brendan Gregg) — эксперт по производительности в Linux. Utilization значит, что мы должны для каждого ресурса (CPU, память, диск …) определить, на сколько он используется. Saturation – значит, что мы должны для всех этих ресурсов определить если ли over subscription, например, читаем ли мы 30 файлов с одного диск одновременно. Errors – количество ошибок в программе. После этого можно что-то оптимизировать: будем добавлять процессоры или код улучшать. Это в общем.
Андрей Акиньшин
Первое, что нужно сделать – профилировать и понять, что именно тормозит. Ошибка №1: оптимизация без профилирования. После того как мы нашли узкое место, второй шаг – задать себе вопрос: «Почему этот участок кода тормозит»? Здесь нужно иметь действительно много знаний: нужно догадаться, какие факторы действительно важны и проверять только их, в противном случае придётся потратить очень много времени. И третий шаг – исправить проблему и аккуратно проверить, что ничего не сломалось, и программа действительно теперь работает быстрее.
Дмитрий Иванов
Классики рекомендуют взять профайлер, найти узкие места и пофиксить их. Но когда вы это несколько раз уже делали, узкие места уже размазаны по коду и что-то улучшить уже сложно. Иногда в этих случаях нужно пересмотреть подход, например, вставить по коду Assert’ы, которые убеждаются, что операции завершаются за отведённое время. А дальше исправлять программу и сделать это обязательным для всех программистов. Следующее, что можно сделать – посмотреть на memory traffic и начать переписывать на unsafe, чтобы не генерировать memory traffic.
Какие инструменты — ваши любимые при поиске Performance-проблем?
Саша Гольдштейн
На Windows нужно начинать с Performance Counters. После этого зависит от ситуации. Кучу проблем можно решать только с PerfView, но у него большие проблемы с визуализацией. Visual Studio Profiler – совсем неплох, кстати, у него есть standalone-версия. dotMemory или .NET Memory Profiler– для профилирования памяти.
Андрей Акиньшин
Я являюсь мейнтейнером утилиты BenchmarkDotNet. Последние версии уже вполне юзабельны, с большим количеством возможностей. Тем не менее я постоянно нахожу истории, как люди берут Stopwatch и начинают делать свои самопальные бенчмарки. Проблема каждой второй такой истории: люди запускают бенчмарки в debug’е, потому что это конфигурация выбрана по умолчанию в студии. BenchmarkDotNet не даст запустить бенчмарк в дебаге, соблюдает методологию, запускает код в разных процессах, каждый процесс запускается несколько раз, делает прогрев и т.д. Сейчас вокруг проекта собралось community и инструмент становится удобным для инженерной работы.
В качестве профилирования я могу порекомендовать dotMemory и dotTrace. Я ими часто пользуюсь, и они мне очень-очень нравятся.
Дмитрий Иванов
Все наши инструменты: dotTrace, dotMemory. Почти для всего хватает. Иногда нужно посмотреть на sys internals-утилиты.
Что посоветуете почитать и посмотреть на тему .NET Performance?
Саша Гольдштейн
Есть куча материалов online, есть мои курсы на pluralsight, довольно много информации можно найти в старых блогах Microsoft-разработчиков (Рико Мариани, Крис Брю, Ванс Мориссон). Про книги: есть моя книга Pro .NET Performance, есть книга поновее – Бена Уотсона «High Performance .NET Applications». Тоже достаточно интересно написано. Плюс, нужно что-то знать и понимать про устройство .NET. Есть классическая книга CLR via C#. Она немного устарела, но некоторые моменты просто нигде больше не упоминаются, например, о том, как работают исключения и делегаты.
Андрей Акиньшин
Я бы шёл сверху вниз и не стал бы уходить на уровень CPU, если нет базовых знаний об алгоритмах, структурах данных и о платформе, с которой работаешь. По .NET можно почитать того же Рихтера, разобраться, как работают базовые классы и для чего хороши.
Дальше, хорошо бы разбираться, как работает современно железо, в частности процессоры компании Intel. В этом плане есть много ресурсов в интернете, но это не простое чтиво: нельзя прочитать за вечерок книжку и разобраться, как работает процессор.
Человеку, не специализирующемуся на Performance, а работающему в Enterprise, я советую прокачивать общую эрудицию и расширять кругозор. В этом плане очень здорово ходить на такие конференции как DotNext. Я сам очень люблю смотреть доклады по областям, которыми я профессионально не занимаюсь. У меня в любом случае нет времени профессионально заниматься этой областью, но я буду иметь общее представление, разговаривать с другими программистами. Если мне придется решать задачи, связанные с этой областью, я буду знать, в какие стороны смотреть и что гуглить.
Дмитрий Иванов
Зайти на доклад Саши Гольдштейна, прочитать его книгу, а потом обязательно заняться на работе задачами, требующими оптимизации производительности, иначе
О чём вы будете рассказывать на DotNext в Питере?
Саша Гольдштейн
У меня два доклада: про инструмент для профилирования и про модели памяти. В первом мы будем использовать PerfView. У меня заготовлены примеры кода с performance-проблемами, которые мы будем анализировать. PerfView – это фронтэнд для технологии ETW. Про неё, кстати, есть ещё один доклад на DotNext. ETW – это технология, с помощью которой можно собирать лог. В этот лог пишут многие компоненты Windows, .NET, CLR, ASP.NET. И пишут эти логи с очень большой скоростью. PerfView – это эффективный анализ логов ETW: CPU, время выполнения, доступы к БД, allocation profiling.
Второй доклад — про модели памяти. Memory Model – это «страшное» название для всех операций над памятью на всех высокоуровневых языках. Если программа работает в одном потоке – всё просто, а если потоков много, ситуация меняется. Запись и чтение могут идти не в том порядке, который мы указали, часть доступов к памяти компилятор может вообще оптимизировать. Тоже самое с процессорами. И когда делается порт с Intel на ARM, который во всех айфонах, андройдах, или Power PC, который во всех автомобилях, куча всего перестает работать, потому что процессор позволяет себе больше.
Я собираюсь показать несколько теоретических и практических примеров, когда на Intel работает правильно, а на айфоне ломается или, когда при переходе с одного потока на несколько ломается на Intel, хотя почти никто, глядя на этот код, не может сказать, что есть какая-то проблема. После того, как мы разберем тонкости Memory Model мы перейдём к примерам, как строить правильный multithreaded-код с правильной синхронизацией.
Андрей Акиньшин
Я много раз видел, как программисты допускают досадные ошибки, которые потом неделями ищутся, просто из-за того, что не понимают, как устроены числа с плавающей точкой. Еще меньше людей задумываются о производительности этого кода, о том, насколько быстро может работать и какие простейшие преобразования можно совершить над кодом вашей математической формулы, чтобы она стала работать быстрее.
CPU может распараллеливать команды, чтобы они параллельно запускались на уровне железа, но для этого ваш код, обсчитывающий формулу, должен быть написан особым образом. Если мы нашли узкое место и нужно оптимизировать вычисления, то есть смысл применить дополнительные знания и немного поколдовать над кодом.
Никита Цуканов
Планирую рассказать о Docker как об элементе инфраструктуры для разработки и деплоя. Как быстро и эффективно разворачивать микросервисы и управлять ими. Также я расскажу о том, как запускать Windows Server Core в Docker.
Дмитрий Иванов
Rider – это гибрид Idea и ReSharper. Мы используем общую реактивную модель. Idea выставляет какие-то параметры, на это реагирует R# и выставляет необходимые данные для отрисовки и такой процесс может повторяться несколько раз.
Rider Framework – это библиотека для реактивного взаимодействия Java и .NET и генератор модели из DSL на целевых языках. DSL у нас написан на Kotlin, потому что Kotlin позволяет некоторое мета-программирование в groovy-style, благодаря своим синтаксическим особенностям. В докладе будет live-demo.
Я думаю, что мой доклад будет интересен тем, кто хочет вынести свое приложение out-of-process и/или интересуются реактивными фреймворками и применением реактивной модели для desktop-приложении.
Если у вас есть вопросы к спикерам, вы хотите получить дозу хардкорного .NET и узнать, как и сколько раз изменится погода в Санкт-Петербурге, ждём вас 3 июня на конференции DotNext 2016 Piter.
Автор: JUG.ru Group