Skip to content

Latest commit

 

History

History
99 lines (68 loc) · 9.16 KB

aspnetcore_optimization.md

File metadata and controls

99 lines (68 loc) · 9.16 KB

Оптимизация приложений, разработанных на ASP.NET Core

Рекомендуется к ознакомлению статья "Automatic Resource Management in .NET 7 Core" by Joydip Kanjilal (CODE Manazine Jan-Feb 2024).

Одно из основных направлений оптимизации приложений ASP.NET Core - управление ресурсами. В .NET объекты создаются в трёх разных Heap-ах:

  • Large Object Heap (LOH) - в него сохраняются объекты размером больще 85 КБ
  • Managed Object Heap
  • Unmanaged Object Heap

Лучше всего .NET работает с Managed Object Heap, автоматически удаляя не используемые объекты, контролируя время жизни объектов (life-time cycle) и выполняя компактизацию памяти. С временем жизни объектов связан термин Generation - поколение объектов. Всего есть три поколения - с коротким временем жизни (0), со средней продолжительностью (1) и долгоживующие объекты (2). При сборке мусора, используемые объекты перемещаются из поколения 0 в 1, и из поколения 1 в 2. Поколения 0 и 1 могут быть сброшены после перемещения объектов.

Однако, с Large Object Heap и Unmanaged Object Heap связана целая куча проблем. В частности LOH не сжимается, т.к. размер объектов большой и их копирование может быть долгим. При удалении объекта, область которую он занимал, освобождается и, по факту, это приводит к фрагментации памяти - при создании нового объекта требуется найти ему место подходящего размера.

Выполнять компактизацию LOH можно вручную:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

Объекты, размещённые Unmanaged Object Heap не освобождают ресурсы автоматически при удалении объекта. К таким объектам относятся: file handlers, database connections, internet connections. Классы, которые используют подобные типы объектов должны реализовывать интерфейс IDisposable и реализовывать в нём метод Dispose(), который должен освобождать занятые ресурсы. Чтобы не вызывать освобождение ресурсов объектов удаляемых из Unmanaged Objects Heap можно использовать ключевое слово using, например:

using (var customFileManager = new CustomFileManager())
{
    await customFileManager.FileWriteAsync("This is a text message");
}

Сильные и слабые ссылки

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

MyClass strongReference = new MyClass();    // Объект с сильной связью
WeakReference<MyClass> weakReference = new WeakReference<MyClass>(strongReference); // Объект со слабой связью

CLR может в любой момент удалить объект со слабой связью и по этой причине, всегда нужно проверять, существует ли ещё такой объект:

if (weakReference.IsAlive) {
    MyClass targetObject = (MyClass)weakReference.Target;   // Доступ к объекту, если он ещё жив
    targetObject.CallSomeMethod();
}

Оптимизация кода

DI Container позволяет управлять временем жизни объектов. См. Transient, Scoped и Singleton.

Код на C# нужно очень аккуратно профилировать и рекомендуется активно использовать Benchmarking Class, используя package BenchmarkDotNet.

Рекомендуется активно кэшировать ресурсы и применять Object Pooling - повторно использовать объекты.

Используйте Span<T>, если это алгоритмически возможно.

Рекомендуется маленькие классы определять как struct, поскольку у них нет MethodTable и ObjectHeader. Более того, структуры размещаются на stack-е, тогда как классы на heap-е. Т.е. struct - это value type. Однако нужно помнить о том, что члены структуры, являющиеся ссылочными типами (например - string), будут будут размещаться в heap. Microsoft рекомендует использовать struct в том случае, если все члены структуры занимают 16 байт, или меньше.

Используйте объекты с заранее определённым размером (Pre-Size Data Structures).

Для справки - системные компоненты .NET Core

  • CoreClr - среда исполнения .NET Core, которая предоставляет такие сервисы, как garbage collection, assembly loading, и.т.д. Содержит Just-In-Time компилятор, которые отвечает за трансляцию intermediate code (IL) в машинный код
  • CoreFx - аналог Base Class Library (BCL) из .NET Framework. Базовые библиотеки классов, поддерживающих такой функционал, как serialization, networking, IO, и т.д.
  • CLI and Roslyn - кросс-платформенные инструменты командной строки для разработки, компиляции, выполнения и публикации приложений

Использование контейнеров

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

Необходимо стремиться использовать только generic type-контейнеры, например: List<T>.

Оптимизация работы Linq

Нам следует рассмотреть возможность использовать Select() в LINQ, для того, чтобы уменьшить количество извлекаемых из контейнера данных. Например, у нас есть следующий код:

foreach (var _c in this.Connections)
{
    if (_c.Value.Connection != null && _c.Value.StorageID == storageId)
    {
        return _c.Value.Connection.State == HubConnectionState.Connected;
    }
}

В действительности, нам интересен только объект Value на каждой из итераций работы с контейнером. Мы можем переписать код вот так:

foreach (var _c in this.Connections.Select(_c => _c.Value))
{
    if (_c.Connection != null && _c.StorageID == storageId)
    {
        return _c.Connection.State == HubConnectionState.Connected;
    }
}