Краткое введение
Оптимизация производительности — это обеспечение того, чтобы одинаковое количество запросов обрабатывалось с меньшим количеством ресурсов, которые обычно являются процессором или памятью, а также, конечно, обработкой ввода-вывода операционной системы, сетевым трафиком, использованием диска и т.д. Но чаще всего мы сокращаем использование процессора и памяти. Ранее публикованный контент имеет некоторые ограничения, его сложно трансформировать напрямую, сегодня я хочу поделиться с вами простым методом — нужно заменить лишь несколько типов коллекций, чтобы добиться улучшения производительности и снижения затрат памяти. Сегодня я хочу поделиться с вами учебной библиотекой, вот этойБиблиотека классов называется Collections.Pooled, Как видно из названия, для достижения цели сокращения объема памяти и GC осуществляется через пулированную память, и мы напрямую посмотрим на производительность, а также покажем исходный код, почему он приносит такие улучшения производительности.
Коллекции. Объединены
Ссылка на проект:Вход по гиперссылке виден.
Библиотека основана на классах из System.Collections.Generic, которые были модифицированы для использования новых <T>библиотек классов System.Span и System.Buffers.ArrayPool <T>с целью снижения выделения памяти, повышения производительности и обеспечения большей совместимости с современными API. Collections.Pooled поддерживает .NET Standnd 2.0 (.NET Framework 4.6.1+), а также поддержку . NET Core 2.1+. Обширный набор модульных тестов и бенчмарков был портирован с CoreFX.
Общее количество тестов: 27501. Виза: 27501. Провал: 0. Пропуск: 0. Тестовый запуск прошёл успешно. Время выполнения теста: 9,9019 секунды
Как использовать
Эту библиотеку можно легко установить через Nuget, версия NuGet.
В библиотеке Collections.Pooled реализуются пуловые версии для типов коллекций, которые мы обычно используем, как показано в сравнении с нативными типами .NET.
| . .NET native | Коллекции. Объединены | замечание | | Список<T> | PooledList<T> | Классы общих коллекций | | Словарь<TKey, TValue> | PooledDictionary<TKey, TValue> | Класс общего словаря | | HashSet<T> | PooledSet<T> | Общие классы коллекции хэшей | | Стек<T> | Стек<T> | Общие стеки | | Очередь<T> | PooledQueue<T> | Генерическая когорта |
При использовании нужно добавить только соответствующее . Нативная версия .NET с версией Collections.Pooled, как показано в коде ниже:
Однако следует отметить, что тип Pooled реализует интерфейс IDispose, который возвращает использованную память в пул через метод Dispose(), поэтому нужно вызвать его метод Dispose() после использования объекта коллекции Pooled. Или можно использовать ключевое слово var напрямую.
Примечание: используйте объект коллекции внутри Collections.PooledЛучше всего отпустить его вручную, но неважно, если вы его не выпустите, GC в конце концов переработает его, но он не сможет вернуться в пул, и эффект сохранения памяти не получится. Поскольку он повторно использует пространство памяти, при возврате памяти в пул необходимо обрабатывать элементы коллекции, и он предоставляет перечисление под названием ClearMode, определяемое следующим образом:
По умолчанию можно использовать стандартное значение Auto, а если есть особые требования к производительности, можно использовать Never после того, как зная риски. Для эталонных типов и типов значений, содержащих эталонные типы, необходимо опустошать массив при возврате памяти в пул; если он не очищен, GC не сможет освободить эту часть памяти (потому что ссылка элемента всегда находилась в пуле), если это чистый тип значения, то его нельзя опустошить. В этой статье я описываю разницу памяти между эталонными типами и структурными (value type) массивами, чистые типы значений не имеют переработки заголовков объектов и не требуют вмешательства GC.
. .NET Performance Optimization — используйте альтернативные классы структур:Вход по гиперссылке виден.
Сравнение характеристик
Я не проходил Benchmark в одиночку, и результаты по открытым проектам, которые я использовал напрямую, были равны 0 для использования памяти многих проектов, потому что пулированная память не имела дополнительного выделения.
PooledList<T>
Пройдите по 2048 элементам, добавленным в набор в Benchmark, . .NET Native <T>List требует 110us (согласно фактическим результатам бенчмарка, миллисекунды на рисунке должны быть канцелярской ошибкой) и 263 КБ памяти, тогда как <T>PooledList требует только 36us и 0 КБ памяти.
PooledDictionary<TKey, TValue>
Добавьте 10_0000 элементов в словарь циклом в Benchmark, . .NET Native Dictionary<TKey, TValue> требует 11 мс и 13 МБ памяти, тогда как PooledDictionary<TKey, TValue> требует всего 7 мс и 0 МБ памяти.
PooledSet<T>
Пройдите через коллекцию хэшей в Benchmark и добавьте 10_0000 элементов, . Нативный хэшсет .<T>NET требует 5348 мс и 2 МБ, а PooledSet <T>— всего 4723 мс и 0 МБ памяти.
PooledStack<T>
Пройдите стек в Benchmark, чтобы добавить 10_0000 элементов. Нативный PooledStack для .<T>NET требует 1079 мс и 2 МБ, тогда как PooledStack <T>— только 633 мс и 0 МБ памяти.
PooledQueue<T>
Пройдите циклы в Benchmark, чтобы добавить 10_0000 элементов в очередь, . Нативный <T>.NET PooledQueue требует 681 мс и 1 МБ, тогда как <T>PooledQueue требует только 408 мс и 0 МБ памяти.
Сцена не выпускается вручную
Кроме того, мы уже упоминали, что тип пуловой коллекции должен быть выпущен, но неважно, если он не выйдет, потому что GC будет перерабатывать.
Результаты Benchmark следующие:
Вывод можно сделать из приведённых выше результатов Benchmark.
Выпуск коллекции типов Pooled вовремя едва запускает GC и выделяет память, по графику выделяется всего 56 байт памяти. Даже если коллекция типов Pooled не будет освобождена, поскольку она выделяет память из пула, она всё равно будет повторно использовать память во время операции расширения ReSize и пропускает этап инициализации памяти выделения GC, который происходит относительно быстро. Самый медленный вариант — использовать обычный тип коллекции, каждая операция расширения ReSize должна применяться к новому месту памяти, а также GC должен вернуть прежнее пространство памяти.
Анализ принципов
Если вы читали мой предыдущий блог-пост, вам стоит установить начальный размер для типов коллекций и проанализировать принцип реализации C# Dictionary, вы можете знать, что разработчики .NET BCL используют базовые структуры данных этих базовых типов коллекций как массивы для высокопроизводительного случайного доступа, возьмём <T>List в качестве примера.
Создайте новый массив для хранения добавленных элементов. Если в массиве недостаточно места, запускается операция расширения, чтобы запросить вдвое большее пространство. Код конструктора выглядит следующим образом, и вы можете увидеть, что это общий массив напрямую:
Так что если вы хотите объединить память, достаточно изменить место, где используется новое приложение по ключевому слову в библиотеке классов, чтобы использовать пуловое приложение. Здесь я делюсь им с вами. NET BCL — это тип под названием ArrayPool, который предоставляет пул ресурсов массива с многократно используемыми универсальными экземплярами, которые могут быть использованы для снижения нагрузки на GC и повышения производительности при частом создании и уничтожении массивов.
Базовый слой нашего типа Pooled заключается в использовании ArrayPool для совместного использования пулов ресурсов, и по его конструктору видно, что по умолчанию используется ArrayPool<T>. Общий для назначения объектов массива, и, конечно, вы также можете создать свой собственный ArrayPool для его использования.
Кроме того, при выполнении операции корректировки ёмкости (расширения) старый массив возвращается в пул потоков, а новый массив также получается из пула.
Кроме того, автор использует Span для оптимизации API, таких как Add и Insert, чтобы повысить их производительность в случайном доступе. Кроме того, был добавлен API серии TryXXX, чтобы использовать его более удобно. Например, <T>класс List <T>имеет до 170 модификаций по сравнению с PooledList.
сводка
В нашем онлайн-использовании мы можем заменить родной тип коллекции на тип коллекции, предоставляемый Pooled, что очень помогает снизить использование памяти и задержку P95. Кроме того, даже если вы забудете выпустить её, производительность будет не намного хуже, чем при использовании нативного типа коллекции. Конечно, лучшая привычка — выпускать его вовремя.
Исходный текст:Вход по гиперссылке виден.
|