Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Начало
Если вы уже какое-то время работаете в мире .NET, то знаете, что эта среда выполнения полна скрытых жемчужин. Некоторые из них отточены и дружелюбны, разработаны для того, чтобы новички чувствовали себя в безопасности. Другие — сырые, но мощные инструменты, которым «не обучают в школе», но они дают вам невероятную мощь, если вы знаете, когда и как их использовать. Разберём 5 малоизвестных API, которые могут помочь вам выжать гораздо больше производительности из кода, интенсивно использующего память. Эти функции имеют реальное значение в высокопроизводительных сервисах, конвейерах с малой задержкой и любых приложениях, которые используют много памяти.
1. CollectionsMarshal.AsSpan() для доступа к списку
Обычно, работая с List<T>, вы представляете его себе как удобную и безопасную абстракцию над массивом. Добавляете данные, проходите по списку. Но что, если вам нужен прямой доступ к базовому массиву без копирования?
Стандартный способ:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Нужен Span? Придётся делать копию
Span<int> span = numbers.ToArray().AsSpan();
// вариант 2, но всё равно копия
Span<int> span1 = [.. numbers];
// … работа со span’омToArray() создаёт полную копию содержимого списка, тем самым удваивая размер используемой памяти.
Способ лучше:
using System.Runtime.InteropServices;
…
// Span напрямую над массивом
Span<int> span = CollectionsMarshal.AsSpan(numbers);
…
Почему: здесь мы работаем непосредственно с внутренним хранилищем списка. Никакого копирования, никаких лишних выделений памяти. Есть одна проблема: если список изменит размер (например, при добавлении нового элемента), span станет недействительным. Именно поэтому этот API находится в System.Runtime.InteropServices.
Когда использовать: при обработке больших буферов или выполнении коротких циклов. Это избавляет от ненужных копий, снижает нагрузку на сборщик мусора и повышает производительность.
Выигрыш: в 2 раза по памяти.
2. CollectionsMarshal.GetValueRefOrNullRef для доступа к словарю
Словари повсюду в приложениях .NET. И чаще всего мы просто делаем что-то вроде этого:
if (dict.TryGetValue("foo", out var value))
{
value++;
dict["foo"] = value;
}Выглядит безобидно, но мы ищем ключ дважды: в TryGetValue и в индексаторе.
Способ лучше:
using System.Runtime.InteropServices;
…
ref int valueRef =
ref CollectionsMarshal
.GetValueRefOrNullRef(dict, "foo");
if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;
Почему: С помощью GetValueRefOrNullRef вы получаете ссылку прямо на хранилище словаря. Без повторного поиска, без избыточного вычисления хэша и без лишних записей.
Когда использовать: при работе с большими словарями или циклами, критически важными для производительности (например, с таблицей символов компилятора или кэшем), эти ненужные поиски накапливаются. Этот подход быстрее, но, опять же, слово Unsafe разработчиками языка используется не просто так. Обращайтесь с ref’ами осторожно.
Выигрыш: примерно в 2 раза.
См. также Альтернативный Доступ к Коллекциям в C#13
Окончание следует…
Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7