Рассказы PRO: .NET разработка
- Как ты определяешь, когда приложению требуется оптимизация?
При проектировании приложения все потенциально узкие места обычно наблюдаются через систему мониторинга. На более низком уровне закладываются определённые механизмы, которые в автоматическом режиме замеряют время исполнения этих узких мест, после чего система мониторинга позволяет получать оповещения, когда это время превышает некое установленное значение. Получение такого оповещения указывает на возможную необходимость либо оптимизировать соответствующий алгоритм, либо отмасштабировать его исполнение, в зависимости от наименьшей стоимости работы (с учётом будущего развития).
- Как избегать преждевременной оптимизации кода?
Всегда есть trade-off между скоростью написания какого-либо алгоритма и скоростью его работы. Не всегда такой trade-off является линейным. Со стороны разработчика хорошим подходом, на мой взгляд, было бы осознавать, в первую очередь, принцип KISS (keep it simple, stupid) и, если, на его профессиональный взгляд, разрабатываемый алгоритм можно дешево (читай – быстро) написать оптимально, его следует написать оптимально, но если это дорого, и одновременно с этим, нет гарантий, что в ближайшем будущем потребуется его оптимизация, в таком случае трату времени на эту оптимизацию можно назвать преждевременной. - Какие практики следует использовать для обеспечения масштабируемости приложения?
Масштабируемости выделяют два вида: вертикальная и горизонтальная. Для поддержки эффективной вертикальной масштабируемости (увеличение CPU, RAM и прочего “железа”) программный код, решающий определённую задачу, должен уметь работать эффективнее, если ему дали выделили дополнительные ядра процессора (параллелизм) или оперативки (динамическое кеширование). Для горизонтальной — код должен уметь решать свою задачу с расчётом на то, могут быть запущены дополнительные копии этого кода, и они будут исполняться одновременно (на других нодах/машинах), что влечет за собой, например, проектирование качественной синхронизации — чтобы исполнители друг другу не мешали — и шаринга (sharing) общих ресурсов, чтобы исключить простой работы из-за блокировок этих ресурсов. - Какие инструменты для профилирования и анализа производительности .NET приложений ты используешь?
Сбор метрик в потенциально узких местах. Стандартный профилировщик CPU и RAM в Microsoft Visual Studio. - Как ты ищешь и устраняешь утечки памяти в .NET приложениях?
Если нет догадок, где может быть утечка, то профилировщик Microsoft Visual Studio. Утечки можно разделить на три типа: (1) утечка неуправляемой памяти. Чаще всего где-то разработчик решил выделять нативную память и не учёл исключительную ситуацию, в результате коей память не освобождается. (2) неразумное использование управляемой памяти. Эта ситуация нагружает GC (сборщик мусора), что в свою очередь замедляет работу приложения. Типичная ситуация – это очень частое выделение памяти в куче (heap) под новые объекты. Решением является, например, использование object pool. (3) утечка управляемой памяти. Такое случается, если алгоритм генерирует объекты, и по ошибке разработчика, эти объекты остаются в памяти (например, попадают в список “to be disposed” в корневом scope IServiceProvider-а). Решением является поиск и исправление алгоритма, чтобы ссылок на такие объекты не оставалось (в случае с IServiceProvider — использовать выделенный scope, который потом освобождать) - Как работает сборка мусора (garbage collection) в .NET Framework?
Сборщик выполняет важную роль по спасения разработчика от необходимости вручную освобождать память из под более-неиспользуемых объектов, находящихся в области памяти под названием куча (heap). Сборщик работает в автоматическом режиме. При сборке он временно замораживает работу приложения, позволяя отыскать цепочки объектов (построением графа), на которые более не существует никаких ссылок, после чего может произвести дефрагментацию памяти. - Какие инструменты и техники помогают выявить узкие места в использовании памяти?
Понимание того, как именно работает выделение и сборка памяти, использование пула объектов (при необходимости), использование стека вместо кучи (по возможности). Также не помешает понимание влияния на память коллекций .NET, включая ServiceProvider и строковых объектов. - Какие преимущества имеет многоуровневая архитектура приложения (N-Tier)?
Я бы описал это так: разделение приложения на отдельные уровни является частным случаем применения метода абстракции (абстрагирования), что, как следствие, сильно упрощает работу по разработке и поддержке приложения. Чтобы понять почему это так, можно представить, что вам поставили задачу по ремонту автомобиля, но вы, как человек, умеющий применять абстрактный подход, можете одним лишь мановением руки физически телепортировать, например, всю тормозную систему автомобиля себе в гараж, где со всеми удобствами в виде мягкого кресла, горячего кофе и никотинового вейпа, исследовать все неисправности, внести нужные изменения, и телепортировать её обратно. Согласитесь, звучит практичнее, чем находиться полдня под подъёмником с набором ключей в руках и затёкшей от запрокидывания головы шеей. - Какие советы ты можешь дать по оптимизации работы приложения с большим объемом данных?
Это очень широкий вопрос, и ответ сильно зависит от множества факторов и требований к системе, такими как отказоустойчивость, доступность, время ответа, консистентность данных. Чаще всего оптимизация осуществляется индексированием или кешированием данных, что упрощает поиск. Отказоустойчивость и доступность достигаются стратегическим распределением рабочих компонентов, удалением Single Point of Failure. Время ответа зависит как от оптимальности алгоритмов, обрабатывающих запрос, так и от распределения этой нагрузки, когда и оптимальности становится недостаточно. Консистентность реализуется подходящей структурой всей системы приложений, если используется микро сервисный подход.