Не только PVS-Studio (спасибо им за ключ) имеет проблемы с аллокацией памяти. Вот тут JetBrains рассказывают следующее:
- Надо выбрать хороший алгоритм. В них я разбираюсь плохо, поэтому советов давать не буду: какой-то вариант обхода графа объектов. Важно то, что подбор правильного алгоритма позволил снизить аллокацию с 1.17ГБ до 7.5МБ в секунду. Да, он стал медленнее, но оптимизация это вечная борьба между памятью и CPU.
- Не нужно создавать миллион Dictionary, даже если вам это очень удобно. Это достаточно сложный класс, который внутри себя содержит два массива, которые он ещё и периодически расширяет. Да, он очень удобен, но в горячем месте кода такое удобство может очень больно выстрелить.
- Переиспользование коллекций иногда может быть плохой идеей. Например, если Dictionary ранее содержал 20 тысяч элементов, то переиспользовать его не нужно. Это, например, сильно медленнее при очистке (с чем и столкнулись JetBrains). Тут есть два выхода. Либо писать свою коллекцию, которая не очищается, а просто переставляет указатель на первый элемент внутреннего массива. Либо сразу отбрасывать переиспользованную коллекцию, элементов в которой стало больше какого-то предела.
- Предпочитайте struct, если объем данных очень большой. Дело в том, что структура не имеет заголовочных данных класса, а значит занимает меньше памяти. В случае JetBrains, объем полезных данных в их классе всего 45 байт. Однако, с учетом заголовка объекта и выравнивания, экземпляр класса занимает 72 байта. Казалось бы копейки, но на большом объеме это будет бить очень больно.
- При переходе на структуры, нужно стараться уменьшить количество полей в структуре, чтобы она занимала меньше памяти. Однако, если мы боремся за CPU, то предвычисленные значения предпочтительнее.
- Обход по массиву структур несколько быстрее обхода массива классов, поскольку данные лежат сильно компактнее и их удобнее читать. На этом эффекте основана производительность некоторых ECS фреймворков для игр.
Коллеги не сделали относительных выводов по данной оптимизации и я их понимаю: приложение вообще отказывалось работать. Тем не менее, я взял максимумы из начала статьи и вынул калькулятор. Итак, увеличение скорости составило больше 90% (с 55 минут до 2 минут), а потребление памяти снизилось на 40% (с 20ГБ до 12ГБ).
Мой канал в TG: https://t.me/csharp_gepard